Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable casting PyObject's to primitives; provide type param info for py lists #1490

Merged
merged 14 commits into from
Nov 3, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import io.deephaven.util.type.TypeUtils;
import com.github.javaparser.ast.expr.BinaryExpr;
import org.jpy.PyObject;

import java.io.*;
import java.text.*;
Expand Down Expand Up @@ -319,7 +320,8 @@ public static void main(String args[]) {

buf.append("package io.deephaven.db.tables.lang;\n\n");

buf.append("import io.deephaven.util.QueryConstants;\n\n");
buf.append("import io.deephaven.util.QueryConstants;\n");
buf.append("import org.jpy.PyObject;\n\n");

buf.append("@SuppressWarnings({\"unused\", \"WeakerAccess\", \"SimplifiableIfStatement\"})\n");
buf.append("public final class DBLanguageFunctionUtil {\n\n");
Expand Down Expand Up @@ -558,6 +560,118 @@ public static void main(String args[]) {
append(buf, castFromObjFormatter, BinaryExpr.Operator.PLUS, c, Object.class);
}

// Special casts for PyObject to primitive
buf.append(" public static int intPyCast(Object a) {\n");
nbauernfeind marked this conversation as resolved.
Show resolved Hide resolved
buf.append(" if (a != null && !(a instanceof PyObject)) {\n");
buf.append(" throw new IllegalArgumentException(\"Provided value is not a PyObject\");\n");
buf.append(" }\n");
buf.append(" PyObject o = (PyObject) a;\n");
buf.append(" if (o == null || o.isNone()) {\n");
buf.append(" return QueryConstants.NULL_INT;\n");
buf.append(" }\n");
buf.append(" return o.getIntValue();\n");
buf.append(" }\n\n");

buf.append(" public static double doublePyCast(Object a) {\n");
buf.append(" if (a != null && !(a instanceof PyObject)) {\n");
buf.append(" throw new IllegalArgumentException(\"Provided value is not a PyObject\");\n");
buf.append(" }\n");
buf.append(" PyObject o = (PyObject) a;\n");
buf.append(" if (o == null || o.isNone()) {\n");
buf.append(" return QueryConstants.NULL_DOUBLE;\n");
buf.append(" }\n");
buf.append(" return o.getDoubleValue();\n");
buf.append(" }\n\n");

buf.append(" public static long longPyCast(Object a) {\n");
buf.append(" if (a != null && !(a instanceof PyObject)) {\n");
buf.append(" throw new IllegalArgumentException(\"Provided value is not a PyObject\");\n");
buf.append(" }\n");
buf.append(" PyObject o = (PyObject) a;\n");
buf.append(" if (o == null || o.isNone()) {\n");
buf.append(" return QueryConstants.NULL_LONG;\n");
buf.append(" }\n");
buf.append(" return o.getLongValue();\n");
buf.append(" }\n\n");

buf.append(" public static float floatPyCast(Object a) {\n");
buf.append(" if (a != null && !(a instanceof PyObject)) {\n");
buf.append(" throw new IllegalArgumentException(\"Provided value is not a PyObject\");\n");
buf.append(" }\n");
buf.append(" PyObject o = (PyObject) a;\n");
buf.append(" if (o == null || o.isNone()) {\n");
buf.append(" return QueryConstants.NULL_FLOAT;\n");
buf.append(" }\n");
buf.append(" return floatCast(o.getDoubleValue());\n");
nbauernfeind marked this conversation as resolved.
Show resolved Hide resolved
buf.append(" }\n\n");

buf.append(" public static char charPyCast(Object a) {\n");
buf.append(" if (a != null && !(a instanceof PyObject)) {\n");
buf.append(" throw new IllegalArgumentException(\"Provided value is not a PyObject\");\n");
buf.append(" }\n");
buf.append(" PyObject o = (PyObject) a;\n");
buf.append(" if (o == null || o.isNone()) {\n");
buf.append(" return QueryConstants.NULL_CHAR;\n");
buf.append(" }\n");
buf.append(" return charCast(o.getIntValue());\n");
nbauernfeind marked this conversation as resolved.
Show resolved Hide resolved
buf.append(" }\n\n");

buf.append(" public static byte bytePyCast(Object a) {\n");
buf.append(" if (a != null && !(a instanceof PyObject)) {\n");
buf.append(" throw new IllegalArgumentException(\"Provided value is not a PyObject\");\n");
buf.append(" }\n");
buf.append(" PyObject o = (PyObject) a;\n");
buf.append(" if (o == null || o.isNone()) {\n");
buf.append(" return QueryConstants.NULL_BYTE;\n");
buf.append(" }\n");
buf.append(" return byteCast(o.getIntValue());\n");
nbauernfeind marked this conversation as resolved.
Show resolved Hide resolved
buf.append(" }\n\n");

buf.append(" public static short shortPyCast(Object a) {\n");
buf.append(" if (a != null && !(a instanceof PyObject)) {\n");
buf.append(" throw new IllegalArgumentException(\"Provided value is not a PyObject\");\n");
buf.append(" }\n");
buf.append(" PyObject o = (PyObject) a;\n");
buf.append(" if (o == null || o.isNone()) {\n");
buf.append(" return QueryConstants.NULL_SHORT;\n");
buf.append(" }\n");
buf.append(" return shortCast(o.getIntValue());\n");
nbauernfeind marked this conversation as resolved.
Show resolved Hide resolved
buf.append(" }\n\n");

buf.append(" public static String doStringPyCast(Object a) {\n");
buf.append(" if (a != null && !(a instanceof PyObject)) {\n");
buf.append(" throw new IllegalArgumentException(\"Provided value is not a PyObject\");\n");
buf.append(" }\n");
buf.append(" PyObject o = (PyObject) a;\n");
buf.append(" if (o == null || o.isNone()) {\n");
buf.append(" return null;\n");
buf.append(" }\n");
buf.append(" return o.getStringValue();\n");
buf.append(" }\n\n");

buf.append(" public static boolean booleanPyCast(Object a) {\n");
buf.append(" if (a != null && !(a instanceof PyObject)) {\n");
buf.append(" throw new IllegalArgumentException(\"Provided value is not a PyObject\");\n");
buf.append(" }\n");
buf.append(" PyObject o = (PyObject) a;\n");
buf.append(" if (o == null || o.isNone()) {\n");
buf.append(" throw new NullPointerException(\"Provided value is unexpectedly null;");
buf.append(" cannot cast to boolean\");\n");
buf.append(" }\n");
buf.append(" return o.getBooleanValue();\n");
buf.append(" }\n\n");

buf.append(" public static Boolean doBooleanPyCast(Object a) {\n");
buf.append(" if (a != null && !(a instanceof PyObject)) {\n");
buf.append(" throw new IllegalArgumentException(\"Provided value is not a PyObject\");\n");
buf.append(" }\n");
buf.append(" PyObject o = (PyObject) a;\n");
buf.append(" if (o == null || o.isNone()) {\n");
buf.append(" return null;\n");
buf.append(" }\n");
buf.append(" return o.getBooleanValue();\n");
buf.append(" }\n\n");

// ------------------------------------------------------------------------------------------------------------------------------------------------------------------

classes = new Class[] {int.class, double.class, long.class, float.class, char.class, byte.class, short.class};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package io.deephaven.db.tables.lang;

import io.deephaven.util.QueryConstants;
import org.jpy.PyObject;

@SuppressWarnings({"unused", "WeakerAccess", "SimplifiableIfStatement"})
public final class DBLanguageFunctionUtil {
Expand Down Expand Up @@ -19289,6 +19290,116 @@ public static short shortCast(Object a) {
return a == null ? QueryConstants.NULL_SHORT : (short) a;
}

public static int intPyCast(Object a) {
if (a != null && !(a instanceof PyObject)) {
throw new IllegalArgumentException("Provided value is not a PyObject");
}
PyObject o = (PyObject) a;
if (o == null || o.isNone()) {
return QueryConstants.NULL_INT;
}
return o.getIntValue();
}

public static double doublePyCast(Object a) {
if (a != null && !(a instanceof PyObject)) {
throw new IllegalArgumentException("Provided value is not a PyObject");
}
PyObject o = (PyObject) a;
if (o == null || o.isNone()) {
return QueryConstants.NULL_DOUBLE;
}
return o.getDoubleValue();
}
nbauernfeind marked this conversation as resolved.
Show resolved Hide resolved

public static long longPyCast(Object a) {
if (a != null && !(a instanceof PyObject)) {
throw new IllegalArgumentException("Provided value is not a PyObject");
}
PyObject o = (PyObject) a;
if (o == null || o.isNone()) {
return QueryConstants.NULL_LONG;
}
return o.getLongValue();
}

public static float floatPyCast(Object a) {
if (a != null && !(a instanceof PyObject)) {
throw new IllegalArgumentException("Provided value is not a PyObject");
}
PyObject o = (PyObject) a;
if (o == null || o.isNone()) {
return QueryConstants.NULL_FLOAT;
}
return floatCast(o.getDoubleValue());
nbauernfeind marked this conversation as resolved.
Show resolved Hide resolved
}

public static char charPyCast(Object a) {
if (a != null && !(a instanceof PyObject)) {
throw new IllegalArgumentException("Provided value is not a PyObject");
}
PyObject o = (PyObject) a;
if (o == null || o.isNone()) {
return QueryConstants.NULL_CHAR;
}
return charCast(o.getIntValue());
nbauernfeind marked this conversation as resolved.
Show resolved Hide resolved
}

public static byte bytePyCast(Object a) {
if (a != null && !(a instanceof PyObject)) {
throw new IllegalArgumentException("Provided value is not a PyObject");
}
PyObject o = (PyObject) a;
if (o == null || o.isNone()) {
return QueryConstants.NULL_BYTE;
}
return byteCast(o.getIntValue());
nbauernfeind marked this conversation as resolved.
Show resolved Hide resolved
}

public static short shortPyCast(Object a) {
if (a != null && !(a instanceof PyObject)) {
throw new IllegalArgumentException("Provided value is not a PyObject");
}
PyObject o = (PyObject) a;
if (o == null || o.isNone()) {
return QueryConstants.NULL_SHORT;
}
return shortCast(o.getIntValue());
nbauernfeind marked this conversation as resolved.
Show resolved Hide resolved
}

public static String doStringPyCast(Object a) {
if (a != null && !(a instanceof PyObject)) {
throw new IllegalArgumentException("Provided value is not a PyObject");
}
PyObject o = (PyObject) a;
if (o == null || o.isNone()) {
return null;
}
return o.getStringValue();
}

public static boolean booleanPyCast(Object a) {
nbauernfeind marked this conversation as resolved.
Show resolved Hide resolved
if (a != null && !(a instanceof PyObject)) {
throw new IllegalArgumentException("Provided value is not a PyObject");
}
PyObject o = (PyObject) a;
if (o == null || o.isNone()) {
throw new NullPointerException("Provided value is unexpectedly null; cannot cast to boolean");
}
return o.getBooleanValue();
}

public static Boolean doBooleanPyCast(Object a) {
if (a != null && !(a instanceof PyObject)) {
throw new IllegalArgumentException("Provided value is not a PyObject");
}
PyObject o = (PyObject) a;
if (o == null || o.isNone()) {
return null;
}
return o.getBooleanValue();
}

public static int negate(int a) {
return a == QueryConstants.NULL_INT ? QueryConstants.NULL_INT : -a;
}
Expand Down
40 changes: 33 additions & 7 deletions DB/src/main/java/io/deephaven/db/tables/lang/DBLanguageParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -1003,11 +1003,22 @@ public Class<?> visit(ArrayAccessExpr n, VisitArgs printer) {
Class<?> paramType = n.getIndex().accept(this, printer);
printer.append(')');

if (DbArray.class.isAssignableFrom(type) && (n.getName() instanceof NameExpr)) {
Class<?> ret = variableParameterizedTypes.get(((NameExpr) n.getName()).getNameAsString())[0];
// We'll check for a known component type if this a NameExpr.
if (n.getName() instanceof NameExpr) {
rcaudy marked this conversation as resolved.
Show resolved Hide resolved
Class<?>[] classes = variableParameterizedTypes.get(((NameExpr) n.getName()).getNameAsString());

if (ret != null) {
return ret;
if (classes != null) {
Class<?> ret = null;

if (classes.length == 1) {
ret = classes[0]; // scenario 1: this is a list-like type
} else if (classes.length == 2) {
ret = classes[1]; // scenario 2: this is a map-like type
}

if (ret != null) {
return ret;
}
}
}

Expand Down Expand Up @@ -1166,14 +1177,27 @@ else if (fromBoxedType) {
* Now actually print the cast. For casts to primitives (except boolean), we use special null-safe functions
* (e.g. intCast()) to perform the cast.
*
* There is no "booleanCast()" function.
* There is no "booleanCast()" function. However, we do try to cast to String and Boolean from PyObjects.
*
* There are also no special functions for the identity conversion -- e.g. "intCast(int)"
*/
if (toPrimitive && !ret.equals(boolean.class) && !ret.equals(exprType)) {
final boolean isPyUpgrade =
((ret.equals(boolean.class) || ret.equals(Boolean.class) || ret.equals(String.class))
&& exprType.equals(PyObject.class));

if ((toPrimitive && !ret.equals(boolean.class) && !ret.equals(exprType)) || isPyUpgrade) {
// Casting to a primitive, except booleans and the identity conversion
if (!toPrimitive) {
// these methods look like `doStringPyCast` and `doBooleanPyCast`
printer.append("do");
}
printer.append(ret.getSimpleName());
printer.append("Cast(");

if (exprType != NULL_CLASS && isAssignableFrom(PyObject.class, exprType)) {
printer.append("PyCast(");
} else {
printer.append("Cast(");
}

/*
* When unboxing to a wider type, do an unboxing conversion followed by a widening conversion. See table
Expand Down Expand Up @@ -1488,6 +1512,8 @@ public Class<?> visit(FieldAccessExpr n, VisitArgs printer) {
// The to-be-cast expr is a Python object field accessor
final String clsName = printer.pythonCastContext.getSimpleName();
printer.append(", " + clsName + ".class");
// Let's advertise to the caller the cast context type
ret = printer.pythonCastContext;
}
printer.append(')');
} else {
Expand Down
Loading