Skip to content

Commit

Permalink
Enable casting PyObject's to primitives; provide type param info for …
Browse files Browse the repository at this point in the history
…py lists (#1490)

Also did this for String/boolean/Boolean:

from deephaven import TableTools
x = ['0', '1', '42', None]
y = TableTools.emptyTable(1).update("X = (String)x[i % x.size()]")
print(y.getDefinition())
Note that after some discussion we preferred to NPE when casting to boolean and the value is a None. We also decided that a String cast should throw an exception if not already a String (instead of converting via PyObject::str).

Fixes #1009
  • Loading branch information
nbauernfeind authored Nov 3, 2021
1 parent 5153657 commit 9e60ab4
Show file tree
Hide file tree
Showing 9 changed files with 393 additions and 42 deletions.
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");
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 (float) o.getDoubleValue();\n");
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 (char) o.getIntValue();\n");
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 (byte) o.getIntValue();\n");
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 (short) o.getIntValue();\n");
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();
}

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 (float) o.getDoubleValue();
}

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 (char) o.getIntValue();
}

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 (byte) o.getIntValue();
}

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 (short) o.getIntValue();
}

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) {
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) {
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

0 comments on commit 9e60ab4

Please sign in to comment.