Builtin methods can support array-like arguments (#7235)

This PR modifies the builtin method processor such that it forbids arrays of non-primitive and non-guest objects in builtin methods. And provides a proper implementation for the builtin methods in `EnsoFile`.

- Remove last `to_array` calls from `File.enso`
This commit is contained in:
Pavel Marek 2023-07-17 09:17:39 +02:00 committed by GitHub
parent 3d2c0c0154
commit 7264d81f2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 210 additions and 56 deletions

View File

@ -959,6 +959,15 @@ lazy val `interpreter-dsl-test` =
"-Dgraalvm.locatorDisabled=true", "-Dgraalvm.locatorDisabled=true",
s"--upgrade-module-path=${file("engine/runtime/build-cache/truffle-api.jar").absolutePath}" s"--upgrade-module-path=${file("engine/runtime/build-cache/truffle-api.jar").absolutePath}"
), ),
Test / javacOptions ++= Seq(
"-s",
(Test / sourceManaged).value.getAbsolutePath
),
Compile / logManager :=
sbt.internal.util.CustomLogManager.excludeMsg(
"Could not determine source for class ",
Level.Warn
),
commands += WithDebugCommand.withDebug, commands += WithDebugCommand.withDebug,
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"org.graalvm.truffle" % "truffle-api" % graalVersion % "provided", "org.graalvm.truffle" % "truffle-api" % graalVersion % "provided",

View File

@ -36,6 +36,7 @@ polyglot java import java.io.OutputStream as Java_Output_Stream
polyglot java import java.nio.file.FileSystems polyglot java import java.nio.file.FileSystems
polyglot java import java.nio.file.Path polyglot java import java.nio.file.Path
polyglot java import java.nio.file.StandardCopyOption polyglot java import java.nio.file.StandardCopyOption
polyglot java import java.nio.file.StandardOpenOption
polyglot java import java.time.ZonedDateTime polyglot java import java.time.ZonedDateTime
polyglot java import org.enso.base.DryRunFileManager polyglot java import org.enso.base.DryRunFileManager
polyglot java import org.enso.base.encoding.ReportingStreamDecoder polyglot java import org.enso.base.encoding.ReportingStreamDecoder
@ -145,9 +146,9 @@ type File
with_output_stream self open_options action = with_output_stream self open_options action =
new_output_stream : File -> Vector File_Access -> Output_Stream ! File_Error new_output_stream : File -> Vector File_Access -> Output_Stream ! File_Error
new_output_stream file open_options = new_output_stream file open_options =
opts = open_options . map (_.to_java) . to_array opts = open_options . map (_.to_java)
stream = File_Error.handle_java_exceptions file <| stream = File_Error.handle_java_exceptions file <|
file.output_stream_builtin opts.to_array file.output_stream_builtin opts
## We re-wrap the File Not Found error to return the parent directory ## We re-wrap the File Not Found error to return the parent directory
instead of the file itself - because the file that is being written instead of the file itself - because the file that is being written
may not exist and it will not be an error, it is the parent directory may not exist and it will not be an error, it is the parent directory
@ -180,9 +181,10 @@ type File
`File.with_input_stream` instead, which does resource management. `File.with_input_stream` instead, which does resource management.
Arguments: Arguments:
- open_options: A vector of `File_Access` objects determining how to open - open_options: A vector of `StandardOpenOption` polyglot objects
the stream. These options set the access properties of the stream. determining how to open the stream. These options set the access
input_stream : Vector File_Access -> Input_Stream properties of the stream.
input_stream : Vector StandardOpenOption -> Input_Stream
input_stream self options = @Builtin_Method "File.input_stream" input_stream self options = @Builtin_Method "File.input_stream"
## PRIVATE ## PRIVATE
@ -564,8 +566,8 @@ type File
File_Error.handle_java_exceptions self <| case replace_existing of File_Error.handle_java_exceptions self <| case replace_existing of
True -> True ->
copy_options = [StandardCopyOption.REPLACE_EXISTING] copy_options = [StandardCopyOption.REPLACE_EXISTING]
self.copy_builtin destination copy_options.to_array self.copy_builtin destination copy_options
False -> self.copy_builtin destination [].to_array False -> self.copy_builtin destination []
## Moves the file to the specified destination. ## Moves the file to the specified destination.
@ -579,8 +581,8 @@ type File
File_Error.handle_java_exceptions self <| case replace_existing of File_Error.handle_java_exceptions self <| case replace_existing of
True -> True ->
copy_options = [StandardCopyOption.REPLACE_EXISTING] copy_options = [StandardCopyOption.REPLACE_EXISTING]
self.move_builtin destination copy_options.to_array self.move_builtin destination copy_options
False -> self.move_builtin destination [].to_array False -> self.move_builtin destination []
## Deletes the file if it exists on disk. ## Deletes the file if it exists on disk.
@ -609,8 +611,8 @@ type File
new_input_stream : Vector File_Access -> Input_Stream ! File_Error new_input_stream : Vector File_Access -> Input_Stream ! File_Error
new_input_stream self open_options = new_input_stream self open_options =
if self.is_directory then Error.throw (File_Error.IO_Error self "File '"+self.path+"' is a directory") else if self.is_directory then Error.throw (File_Error.IO_Error self "File '"+self.path+"' is a directory") else
opts = open_options . map (_.to_java) . to_array opts = open_options . map (_.to_java)
stream = File_Error.handle_java_exceptions self (self.input_stream opts.to_array) stream = File_Error.handle_java_exceptions self (self.input_stream opts)
resource = Managed_Resource.register stream close_stream resource = Managed_Resource.register stream close_stream
Input_Stream.Value self resource Input_Stream.Value self resource

View File

@ -4,12 +4,16 @@ import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleFile; import com.oracle.truffle.api.TruffleFile;
import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.library.ExportMessage;
import java.util.function.IntFunction;
import org.enso.interpreter.dsl.Builtin; import org.enso.interpreter.dsl.Builtin;
import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.error.PanicException;
import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary;
import java.io.IOException; import java.io.IOException;
@ -45,17 +49,58 @@ public final class EnsoFile implements TruffleObject {
@Builtin.Method(name = "output_stream_builtin") @Builtin.Method(name = "output_stream_builtin")
@Builtin.WrapException(from = IOException.class) @Builtin.WrapException(from = IOException.class)
@Builtin.ReturningGuestObject @Builtin.ReturningGuestObject
@Builtin.Specialize
@CompilerDirectives.TruffleBoundary @CompilerDirectives.TruffleBoundary
public OutputStream outputStream(OpenOption[] opts) throws IOException { public OutputStream outputStream(Object opts,
return this.truffleFile.newOutputStream(opts); EnsoContext ctx,
@CachedLibrary(limit = "5") InteropLibrary interop) throws IOException {
OpenOption[] openOptions = convertInteropArray(opts, interop, ctx, OpenOption[]::new);
return this.truffleFile.newOutputStream(openOptions);
} }
@Builtin.Method @Builtin.Method
@Builtin.WrapException(from = IOException.class) @Builtin.WrapException(from = IOException.class)
@Builtin.Specialize
@Builtin.ReturningGuestObject @Builtin.ReturningGuestObject
@CompilerDirectives.TruffleBoundary @CompilerDirectives.TruffleBoundary
public InputStream inputStream(OpenOption[] opts) throws IOException { public InputStream inputStream(Object opts,
return this.truffleFile.newInputStream(opts); EnsoContext ctx,
@CachedLibrary(limit = "5") InteropLibrary interop) throws IOException {
OpenOption[] openOptions = convertInteropArray(opts, interop, ctx, OpenOption[]::new);
return this.truffleFile.newInputStream(openOptions);
}
@SuppressWarnings("unchecked")
private static <T> T[] convertInteropArray(Object arr,
InteropLibrary interop,
EnsoContext ctx,
IntFunction<T[]> hostArrayCtor) {
if (!interop.hasArrayElements(arr)) {
var vecType = ctx.getBuiltins().vector().getType();
var typeError = ctx.getBuiltins().error().makeTypeError(vecType, arr, "opts");
throw new PanicException(typeError, interop);
}
T[] hostArr;
try {
int size = Math.toIntExact(interop.getArraySize(arr));
hostArr = hostArrayCtor.apply(size);
for (int i = 0; i < size; i++) {
Object elem = interop.readArrayElement(arr, i);
if (!ctx.getEnvironment().isHostObject(elem)) {
var err = ctx.getBuiltins().error().makeUnsupportedArgumentsError(
new Object[]{arr},
"Arguments to opts should be host objects from java.io package"
);
throw new PanicException(err, interop);
}
hostArr[i] = (T) ctx.getEnvironment().asHostObject(elem);
}
} catch (UnsupportedMessageException | InvalidArrayIndexException e) {
throw new IllegalStateException("Unreachable", e);
} catch (ClassCastException e) {
throw new PanicException(e, interop);
}
return hostArr;
} }
@Builtin.Method(name = "read_last_bytes_builtin") @Builtin.Method(name = "read_last_bytes_builtin")
@ -221,16 +266,24 @@ public final class EnsoFile implements TruffleObject {
@Builtin.Method(name = "copy_builtin", description = "Copy this file to a target destination") @Builtin.Method(name = "copy_builtin", description = "Copy this file to a target destination")
@Builtin.WrapException(from = IOException.class) @Builtin.WrapException(from = IOException.class)
@Builtin.Specialize
@CompilerDirectives.TruffleBoundary @CompilerDirectives.TruffleBoundary
public void copy(EnsoFile target, CopyOption[] options) throws IOException { public void copy(EnsoFile target, Object options,
truffleFile.copy(target.truffleFile, options); @CachedLibrary(limit = "5") InteropLibrary interop,
EnsoContext ctx) throws IOException {
CopyOption[] copyOptions = convertInteropArray(options, interop, ctx, CopyOption[]::new);
truffleFile.copy(target.truffleFile, copyOptions);
} }
@Builtin.Method(name = "move_builtin", description = "Move this file to a target destination") @Builtin.Method(name = "move_builtin", description = "Move this file to a target destination")
@Builtin.WrapException(from = IOException.class) @Builtin.WrapException(from = IOException.class)
@Builtin.Specialize
@CompilerDirectives.TruffleBoundary @CompilerDirectives.TruffleBoundary
public void move(EnsoFile target, CopyOption[] options) throws IOException { public void move(EnsoFile target, Object options,
truffleFile.move(target.truffleFile, options); @CachedLibrary(limit = "5") InteropLibrary interop,
EnsoContext ctx) throws IOException {
CopyOption[] copyOptions = convertInteropArray(options, interop, ctx, CopyOption[]::new);
truffleFile.move(target.truffleFile, copyOptions);
} }
@Builtin.Method @Builtin.Method

View File

@ -178,7 +178,7 @@ public class BuiltinsProcessor extends AbstractProcessor {
try { try {
MethodNodeClassGenerator classGenerator = MethodNodeClassGenerator classGenerator =
new NoSpecializationClassGenerator( new NoSpecializationClassGenerator(
method, builtinMethodNode, ownerClass, stdLibOwnerClass, i); processingEnv, method, builtinMethodNode, ownerClass, stdLibOwnerClass, i);
classGenerator.generate( classGenerator.generate(
processingEnv, processingEnv,
methodName, methodName,
@ -232,7 +232,7 @@ public class BuiltinsProcessor extends AbstractProcessor {
if (encountered.size() == expected) { if (encountered.size() == expected) {
MethodNodeClassGenerator classGenerator = MethodNodeClassGenerator classGenerator =
new SpecializationClassGenerator( new SpecializationClassGenerator(
encountered, builtinMethodNode, ownerClass, stdLibOwnerClass); processingEnv, encountered, builtinMethodNode, ownerClass, stdLibOwnerClass);
classGenerator.generate( classGenerator.generate(
processingEnv, processingEnv,
builtinMethodName, builtinMethodName,
@ -243,7 +243,7 @@ public class BuiltinsProcessor extends AbstractProcessor {
} else { } else {
MethodNodeClassGenerator classGenerator = MethodNodeClassGenerator classGenerator =
new NoSpecializationClassGenerator( new NoSpecializationClassGenerator(
method, builtinMethodNode, ownerClass, stdLibOwnerClass); processingEnv, method, builtinMethodNode, ownerClass, stdLibOwnerClass);
classGenerator.generate( classGenerator.generate(
processingEnv, processingEnv,
builtinMethodName, builtinMethodName,

View File

@ -144,7 +144,6 @@ public class MethodProcessor extends BuiltinsMetadataProcessor<MethodProcessor.M
private void generateCode(MethodDefinition methodDefinition) throws IOException { private void generateCode(MethodDefinition methodDefinition) throws IOException {
JavaFileObject gen = JavaFileObject gen =
processingEnv.getFiler().createSourceFile(methodDefinition.getQualifiedName()); processingEnv.getFiler().createSourceFile(methodDefinition.getQualifiedName());
Set<String> allImports = new HashSet<>(necessaryImports); Set<String> allImports = new HashSet<>(necessaryImports);
allImports.addAll(methodDefinition.getImports()); allImports.addAll(methodDefinition.getImports());
@ -156,6 +155,10 @@ public class MethodProcessor extends BuiltinsMetadataProcessor<MethodProcessor.M
out.println(); out.println();
out.println("/**");
out.println(" * Generated by {@link " + getClass().getName() + "}.");
out.println(" * From {@link " + methodDefinition.getOriginalClassName() + "}.");
out.println(" */");
out.println("@NodeInfo("); out.println("@NodeInfo(");
out.println(" shortName = \"" + methodDefinition.getDeclaredName() + "\","); out.println(" shortName = \"" + methodDefinition.getDeclaredName() + "\",");
out.println(" description = \"\"\"\n" + methodDefinition.getDescription() + "\"\"\")"); out.println(" description = \"\"\"\n" + methodDefinition.getDescription() + "\"\"\")");
@ -165,13 +168,13 @@ public class MethodProcessor extends BuiltinsMetadataProcessor<MethodProcessor.M
out.println("public class " + methodDefinition.getClassName() + " extends BuiltinRootNode implements InlineableNode.Root {"); out.println("public class " + methodDefinition.getClassName() + " extends BuiltinRootNode implements InlineableNode.Root {");
} }
out.println(" private @Child " + methodDefinition.getOriginalClassName() + " bodyNode;"); out.println(" private @Child " + methodDefinition.getOriginalClassName() + " bodyNode;");
out.println();
out.println(" private static final class Internals {"); out.println(" private static final class Internals {");
out.println(" Internals(boolean s) {"); out.println(" Internals(boolean s) {");
out.println(" this.staticOfInstanceMethod = s;"); out.println(" this.staticOfInstanceMethod = s;");
out.println(" }"); out.println(" }");
out.println(" private final boolean staticOfInstanceMethod;");
out.println(); out.println();
out.println(" private final boolean staticOfInstanceMethod;");
for (MethodDefinition.ArgumentDefinition arg : methodDefinition.getArguments()) { for (MethodDefinition.ArgumentDefinition arg : methodDefinition.getArguments()) {
if (arg.shouldCheckErrors()) { if (arg.shouldCheckErrors()) {
@ -278,6 +281,7 @@ public class MethodProcessor extends BuiltinsMetadataProcessor<MethodProcessor.M
generateWarningsCheck(out, methodDefinition.getArguments(), "arguments"); generateWarningsCheck(out, methodDefinition.getArguments(), "arguments");
for (MethodDefinition.ArgumentDefinition argumentDefinition : for (MethodDefinition.ArgumentDefinition argumentDefinition :
methodDefinition.getArguments()) { methodDefinition.getArguments()) {
out.println(" /*** Start of processing argument " + argumentDefinition.getPosition() + " ***/");
if (argumentDefinition.isImplicit()) { if (argumentDefinition.isImplicit()) {
} else if (argumentDefinition.isState()) { } else if (argumentDefinition.isState()) {
callArgNames.add("state"); callArgNames.add("state");
@ -289,6 +293,7 @@ public class MethodProcessor extends BuiltinsMetadataProcessor<MethodProcessor.M
callArgNames.add(mkArgumentInternalVarName(argumentDefinition)); callArgNames.add(mkArgumentInternalVarName(argumentDefinition));
generateArgumentRead(out, argumentDefinition, "arguments"); generateArgumentRead(out, argumentDefinition, "arguments");
} }
out.println(" /*** End of processing argument " + argumentDefinition.getPosition() + " ***/");
} }
String executeCall = "bodyNode.execute(" + String.join(", ", callArgNames) + ")"; String executeCall = "bodyNode.execute(" + String.join(", ", callArgNames) + ")";
if (warningsPossible) { if (warningsPossible) {

View File

@ -19,8 +19,12 @@ public final class ExecuteMethodImplGenerator extends MethodGenerator {
private final boolean needsVarargExpansion; private final boolean needsVarargExpansion;
public ExecuteMethodImplGenerator( public ExecuteMethodImplGenerator(
ExecutableElement method, boolean convertToGuestValue, int expandedVarargs) { ProcessingEnvironment processingEnvironment,
ExecutableElement method,
boolean convertToGuestValue,
int expandedVarargs) {
this( this(
processingEnvironment,
method, method,
method.getReturnType().toString(), method.getReturnType().toString(),
method.getModifiers().contains(Modifier.STATIC), method.getModifiers().contains(Modifier.STATIC),
@ -31,6 +35,7 @@ public final class ExecuteMethodImplGenerator extends MethodGenerator {
} }
private ExecuteMethodImplGenerator( private ExecuteMethodImplGenerator(
ProcessingEnvironment processingEnvironment,
ExecutableElement method, ExecutableElement method,
String returnTpe, String returnTpe,
boolean isStatic, boolean isStatic,
@ -38,7 +43,12 @@ public final class ExecuteMethodImplGenerator extends MethodGenerator {
boolean convertToGuestValue, boolean convertToGuestValue,
int expandedVarargs, int expandedVarargs,
boolean isVarargs) { boolean isVarargs) {
super(isStatic, isConstructor, convertToGuestValue, TypeWithKind.createFromTpe(returnTpe)); super(
processingEnvironment,
isStatic,
isConstructor,
convertToGuestValue,
TypeWithKind.createFromTpe(returnTpe));
this.method = method; this.method = method;
this.varargExpansion = expandedVarargs; this.varargExpansion = expandedVarargs;
this.needsVarargExpansion = isVarargs && (varargExpansion > 0); this.needsVarargExpansion = isVarargs && (varargExpansion > 0);
@ -112,6 +122,15 @@ public final class ExecuteMethodImplGenerator extends MethodGenerator {
if (returnTpe.isValidGuestType()) { if (returnTpe.isValidGuestType()) {
return new String[] {" return " + qual + "." + name + "(" + paramsApplied + ");"}; return new String[] {" return " + qual + "." + name + "(" + paramsApplied + ");"};
} else { } else {
if (!convertToGuestValue) {
processingEnvironment
.getMessager()
.printMessage(
javax.tools.Diagnostic.Kind.ERROR,
"Cannot generate method body for "
+ method
+ " because it returns a host object and convertToGuestValue is false");
}
return new String[] { return new String[] {
" return context", " return context",
" .getEnvironment()", " .getEnvironment()",
@ -134,14 +153,16 @@ public final class ExecuteMethodImplGenerator extends MethodGenerator {
return result || params.stream().anyMatch(p -> p.needsToHostTranslation()); return result || params.stream().anyMatch(p -> p.needsToHostTranslation());
} }
public List<String> generate(ProcessingEnvironment processingEnv, String name, String owner) { public List<String> generate(String name, String owner) {
SafeWrapException[] exceptionWrappers = wrapExceptions(processingEnvironment, method);
SafeWrapException[] exceptionWrappers = wrapExceptions(processingEnv, method);
boolean wrapsExceptions = exceptionWrappers.length != 0; boolean wrapsExceptions = exceptionWrappers.length != 0;
List<? extends VariableElement> rawParams = method.getParameters(); List<? extends VariableElement> rawParams = method.getParameters();
List<MethodParameter> params = List<MethodParameter> params =
IntStream.range(0, method.getParameters().size()) IntStream.range(0, method.getParameters().size())
.mapToObj(i -> fromVariableElementToMethodParameter(i, rawParams.get(i))) .mapToObj(
i ->
fromVariableElementToMethodParameter(
processingEnvironment, i, rawParams.get(i)))
.collect(Collectors.toList()); .collect(Collectors.toList());
String[] body = bodyBase(name, owner, params); String[] body = bodyBase(name, owner, params);

View File

@ -14,14 +14,16 @@ import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.*; import javax.lang.model.element.*;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types; import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.enso.interpreter.dsl.Builtin; import org.enso.interpreter.dsl.Builtin;
public abstract class MethodGenerator { public abstract class MethodGenerator {
protected final boolean isStatic; protected final boolean isStatic;
protected final boolean isConstructor; protected final boolean isConstructor;
private final boolean convertToGuestValue; protected final boolean convertToGuestValue;
protected final TypeWithKind returnTpe; protected final TypeWithKind returnTpe;
protected final ProcessingEnvironment processingEnvironment;
private static final WrapExceptionExtractor wrapExceptionsExtractor = private static final WrapExceptionExtractor wrapExceptionsExtractor =
new WrapExceptionExtractor(Builtin.WrapException.class, Builtin.WrapExceptions.class); new WrapExceptionExtractor(Builtin.WrapException.class, Builtin.WrapExceptions.class);
@ -32,18 +34,19 @@ public abstract class MethodGenerator {
} }
public MethodGenerator( public MethodGenerator(
ProcessingEnvironment processingEnvironment,
boolean isStatic, boolean isStatic,
boolean isConstructor, boolean isConstructor,
boolean convertToGuestValue, boolean convertToGuestValue,
TypeWithKind returnTpe) { TypeWithKind returnTpe) {
this.processingEnvironment = processingEnvironment;
this.isStatic = isStatic; this.isStatic = isStatic;
this.isConstructor = isConstructor; this.isConstructor = isConstructor;
this.convertToGuestValue = convertToGuestValue; this.convertToGuestValue = convertToGuestValue;
this.returnTpe = returnTpe; this.returnTpe = returnTpe;
} }
public abstract List<String> generate( public abstract List<String> generate(String name, String owner);
ProcessingEnvironment processingEnv, String name, String owner);
/** /**
* Generate node's `execute` method definition (return type and necessary parameters). ' * Generate node's `execute` method definition (return type and necessary parameters). '
@ -91,10 +94,13 @@ public abstract class MethodGenerator {
return tpe.baseType(); return tpe.baseType();
} else { } else {
if (!convertToGuestValue) { if (!convertToGuestValue) {
throw new RuntimeException( processingEnvironment
"If intended, automatic conversion of value of type " .getMessager()
+ tpe.baseType() .printMessage(
+ " to guest value requires explicit '@Builtin.ReturningGuestObject' annotation"); Kind.ERROR,
"Automatic conversion of value of type "
+ tpe.baseType()
+ " to guest value requires explicit '@Builtin.ReturningGuestObject' annotation");
} }
return "Object"; return "Object";
} }
@ -111,14 +117,28 @@ public abstract class MethodGenerator {
* @param v variable element representing the parameter * @param v variable element representing the parameter
* @return MethodParameter encapsulating the method's parameter info * @return MethodParameter encapsulating the method's parameter info
*/ */
protected MethodParameter fromVariableElementToMethodParameter(int i, VariableElement v) { protected MethodParameter fromVariableElementToMethodParameter(
ProcessingEnvironment processingEnv, int i, VariableElement v) {
String ensoName = String ensoName =
CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, v.getSimpleName().toString()); CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, v.getSimpleName().toString());
TypeWithKind tpe = TypeWithKind.createFromTpe(v.asType().toString());
if (tpe.kind() == TypeKind.ARRAY && !tpe.isValidGuestType()) {
processingEnv
.getMessager()
.printMessage(
Kind.ERROR,
"Parameter "
+ v
+ " is an array of host objects, which "
+ "is not supported by the MethodGenerator. Either use array of primitive, or valid guest objects, "
+ "or accept the array as Object and transform it in the method body.",
v);
}
return new MethodParameter( return new MethodParameter(
i, i,
ensoName, ensoName,
v.asType().toString(), v.asType().toString(),
v.getAnnotationMirrors().stream().map(am -> am.toString()).collect(Collectors.toList())); v.getAnnotationMirrors().stream().map(Object::toString).collect(Collectors.toList()));
} }
protected abstract Optional<Integer> expandVararg(int paramsLen, int paramIndex); protected abstract Optional<Integer> expandVararg(int paramsLen, int paramIndex);

View File

@ -42,7 +42,6 @@ public abstract class MethodNodeClassGenerator {
throws IOException { throws IOException {
JavaFileObject gen = JavaFileObject gen =
processingEnv.getFiler().createSourceFile(builtinNode.jvmFriendlyFullyQualifiedName()); processingEnv.getFiler().createSourceFile(builtinNode.jvmFriendlyFullyQualifiedName());
;
try (PrintWriter out = new PrintWriter(gen.openWriter())) { try (PrintWriter out = new PrintWriter(gen.openWriter())) {
String ensoMethodName = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, methodName); String ensoMethodName = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, methodName);
String ensoTypeName = stdlibOwner.name().replaceAll("([a-z])([A-Z])", "$1_$2"); String ensoTypeName = stdlibOwner.name().replaceAll("([a-z])([A-Z])", "$1_$2");
@ -60,6 +59,12 @@ public abstract class MethodNodeClassGenerator {
if (needsFrame != null) { if (needsFrame != null) {
moduleOwnerInfo = moduleOwnerInfo + ", needsFrame = " + needsFrame; moduleOwnerInfo = moduleOwnerInfo + ", needsFrame = " + needsFrame;
} }
out.println("/**");
out.println(
" * Generated by {@link org.enso.interpreter.dsl.builtins.MethodNodeClassGenerator}.");
out.println(
" * From {@link " + ownerClazz.fullyQualifiedName() + "#" + ownerMethodName + "}.");
out.println(" */");
out.println( out.println(
"@BuiltinMethod(type = \"" "@BuiltinMethod(type = \""
+ ensoTypeName + ensoTypeName
@ -83,7 +88,7 @@ public abstract class MethodNodeClassGenerator {
out.println("public class " + builtinNode.jvmFriendlyName() + " extends Node {"); out.println("public class " + builtinNode.jvmFriendlyName() + " extends Node {");
out.println(); out.println();
} }
for (String line : methodsGen().generate(processingEnv, ownerMethodName, ownerClazz.name())) { for (String line : methodsGen().generate(ownerMethodName, ownerClazz.name())) {
out.println(" " + line); out.println(" " + line);
} }
out.println(); out.println();

View File

@ -1,5 +1,6 @@
package org.enso.interpreter.dsl.builtins; package org.enso.interpreter.dsl.builtins;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
/** Generator for builtin method class with no specialization. */ /** Generator for builtin method class with no specialization. */
@ -7,8 +8,10 @@ public final class NoSpecializationClassGenerator extends MethodNodeClassGenerat
ExecutableElement origin; ExecutableElement origin;
int varArgExpansion; int varArgExpansion;
private final ProcessingEnvironment processingEnvironment;
public NoSpecializationClassGenerator( public NoSpecializationClassGenerator(
ProcessingEnvironment processingEnvironment,
ExecutableElement origin, ExecutableElement origin,
ClassName builtinNode, ClassName builtinNode,
ClassName ownerClazz, ClassName ownerClazz,
@ -17,20 +20,22 @@ public final class NoSpecializationClassGenerator extends MethodNodeClassGenerat
super(builtinNode, ownerClazz, stdlibOwner); super(builtinNode, ownerClazz, stdlibOwner);
this.origin = origin; this.origin = origin;
this.varArgExpansion = varArgExpansion; this.varArgExpansion = varArgExpansion;
this.processingEnvironment = processingEnvironment;
} }
public NoSpecializationClassGenerator( public NoSpecializationClassGenerator(
ProcessingEnvironment processingEnvironment,
ExecutableElement origin, ExecutableElement origin,
ClassName builtinNode, ClassName builtinNode,
ClassName ownerClazz, ClassName ownerClazz,
ClassName stdlibOwner) { ClassName stdlibOwner) {
this(origin, builtinNode, ownerClazz, stdlibOwner, 0); this(processingEnvironment, origin, builtinNode, ownerClazz, stdlibOwner, 0);
} }
@Override @Override
protected MethodGenerator methodsGen() { protected MethodGenerator methodsGen() {
return new ExecuteMethodImplGenerator( return new ExecuteMethodImplGenerator(
origin, needsGuestValueConversion(origin), varArgExpansion); processingEnvironment, origin, needsGuestValueConversion(origin), varArgExpansion);
} }
@Override @Override

View File

@ -1,6 +1,7 @@
package org.enso.interpreter.dsl.builtins; package org.enso.interpreter.dsl.builtins;
import java.util.List; import java.util.List;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
/** /**
@ -10,19 +11,22 @@ import javax.lang.model.element.ExecutableElement;
*/ */
public class SpecializationClassGenerator extends MethodNodeClassGenerator { public class SpecializationClassGenerator extends MethodNodeClassGenerator {
List<ExecutableElement> elements; List<ExecutableElement> elements;
private final ProcessingEnvironment processingEnvironment;
public SpecializationClassGenerator( public SpecializationClassGenerator(
ProcessingEnvironment processingEnvironment,
List<ExecutableElement> methodElements, List<ExecutableElement> methodElements,
ClassName builtinNode, ClassName builtinNode,
ClassName ownerClazz, ClassName ownerClazz,
ClassName stdlibOwner) { ClassName stdlibOwner) {
super(builtinNode, ownerClazz, stdlibOwner); super(builtinNode, ownerClazz, stdlibOwner);
this.elements = methodElements; this.elements = methodElements;
this.processingEnvironment = processingEnvironment;
} }
@Override @Override
protected MethodGenerator methodsGen() { protected MethodGenerator methodsGen() {
return new SpecializedMethodsGenerator(elements); return new SpecializedMethodsGenerator(processingEnvironment, elements);
} }
@Override @Override

View File

@ -13,6 +13,7 @@ import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier; import javax.lang.model.element.Modifier;
import javax.lang.model.element.VariableElement; import javax.lang.model.element.VariableElement;
import javax.tools.Diagnostic;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.enso.interpreter.dsl.AcceptsWarning; import org.enso.interpreter.dsl.AcceptsWarning;
import org.enso.interpreter.dsl.Builtin; import org.enso.interpreter.dsl.Builtin;
@ -23,12 +24,17 @@ public final class SpecializedMethodsGenerator extends MethodGenerator {
private static final String WithWarningsClassName = private static final String WithWarningsClassName =
"org.enso.interpreter.runtime.error.WithWarnings"; "org.enso.interpreter.runtime.error.WithWarnings";
public SpecializedMethodsGenerator(List<ExecutableElement> elements) { public SpecializedMethodsGenerator(
this(elements, elements.get(0)); ProcessingEnvironment processingEnvironment, List<ExecutableElement> elements) {
this(processingEnvironment, elements, elements.get(0));
} }
private SpecializedMethodsGenerator(List<ExecutableElement> elements, ExecutableElement first) { private SpecializedMethodsGenerator(
ProcessingEnvironment processingEnvironment,
List<ExecutableElement> elements,
ExecutableElement first) {
this( this(
processingEnvironment,
elements, elements,
first.getModifiers().contains(Modifier.STATIC), first.getModifiers().contains(Modifier.STATIC),
first.getKind() == ElementKind.CONSTRUCTOR, first.getKind() == ElementKind.CONSTRUCTOR,
@ -45,12 +51,13 @@ public final class SpecializedMethodsGenerator extends MethodGenerator {
} }
public SpecializedMethodsGenerator( public SpecializedMethodsGenerator(
ProcessingEnvironment processingEnvironment,
List<ExecutableElement> elements, List<ExecutableElement> elements,
boolean isStatic, boolean isStatic,
boolean isConstructor, boolean isConstructor,
boolean convertToGuestValue, boolean convertToGuestValue,
TypeWithKind returnTpe) { TypeWithKind returnTpe) {
super(isStatic, isConstructor, convertToGuestValue, returnTpe); super(processingEnvironment, isStatic, isConstructor, convertToGuestValue, returnTpe);
this.elements = elements; this.elements = elements;
} }
@ -59,14 +66,14 @@ public final class SpecializedMethodsGenerator extends MethodGenerator {
} }
@Override @Override
public List<String> generate(ProcessingEnvironment processingEnv, String name, String owner) { public List<String> generate(String name, String owner) {
SpecializationMeta meta = inferExecuteParameters(); SpecializationMeta meta = inferExecuteParameters(processingEnvironment);
List<String> result = new ArrayList<>(); List<String> result = new ArrayList<>();
result.add(methodSigDef(owner, meta.execParams(), true)); result.add(methodSigDef(owner, meta.execParams(), true));
result.add(""); result.add("");
result.addAll( result.addAll(
paramsOfSpecializedMethods(processingEnv, meta.diffParam) paramsOfSpecializedMethods(processingEnvironment, meta.diffParam)
.flatMap( .flatMap(
specializeMethod -> specializeMethod ->
specialize(owner, name, specializeMethod, meta.diffParam).stream()) specialize(owner, name, specializeMethod, meta.diffParam).stream())
@ -98,7 +105,10 @@ public final class SpecializedMethodsGenerator extends MethodGenerator {
return new SpecializeMethodInfo( return new SpecializeMethodInfo(
method, method,
IntStream.range(0, params.size()) IntStream.range(0, params.size())
.mapToObj(i -> fromVariableElementToMethodParameter(i, params.get(i))) .mapToObj(
i ->
fromVariableElementToMethodParameter(
processingEnv, i, params.get(i)))
.collect(Collectors.toList()), .collect(Collectors.toList()),
wrapExceptions(processingEnv, method)); wrapExceptions(processingEnv, method));
}); });
@ -132,14 +142,17 @@ public final class SpecializedMethodsGenerator extends MethodGenerator {
return p.tpe().equals("java.lang.Object") || p.tpe().equals("java.lang.String"); return p.tpe().equals("java.lang.Object") || p.tpe().equals("java.lang.String");
} }
private SpecializationMeta inferExecuteParameters() { private SpecializationMeta inferExecuteParameters(ProcessingEnvironment processingEnv) {
Map<Integer, List<MethodParameter>> paramss = Map<Integer, List<MethodParameter>> paramss =
elements.stream() elements.stream()
.flatMap( .flatMap(
method -> { method -> {
List<? extends VariableElement> params = method.getParameters(); List<? extends VariableElement> params = method.getParameters();
return IntStream.range(0, params.size()) return IntStream.range(0, params.size())
.mapToObj(i -> fromVariableElementToMethodParameter(i, params.get(i))); .mapToObj(
i ->
fromVariableElementToMethodParameter(
processingEnv, i, params.get(i)));
}) })
.collect(Collectors.groupingBy(p -> p.index())); .collect(Collectors.groupingBy(p -> p.index()));
@ -268,7 +281,24 @@ public final class SpecializedMethodsGenerator extends MethodGenerator {
" return new Array((Object[]) " + qual + "." + name + "(" + paramsApplied + "));"); " return new Array((Object[]) " + qual + "." + name + "(" + paramsApplied + "));");
break; break;
default: default:
methodBody.add(" return " + qual + "." + name + "(" + paramsApplied + ");"); if (returnTpe.isValidGuestType()) {
methodBody.add(" return " + qual + "." + name + "(" + paramsApplied + ");");
} else if (convertToGuestValue) {
methodBody.add(" var result = " + qual + "." + name + "(" + paramsApplied + ");");
methodBody.add(" return EnsoContext.get(this).getEnvironment().asGuestValue(result);");
} else {
processingEnvironment
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
"Cannot convert return type of "
+ owner
+ "."
+ name
+ " to guest value."
+ " Specify @ReturningGuestValue annotation",
methodInfo.origin);
}
} }
} }