diff --git a/python/cudf_polars/cudf_polars/callback.py b/python/cudf_polars/cudf_polars/callback.py index 76816ee0a61..85fb0d637c6 100644 --- a/python/cudf_polars/cudf_polars/callback.py +++ b/python/cudf_polars/cudf_polars/callback.py @@ -18,7 +18,7 @@ import rmm from rmm._cuda import gpu -from cudf_polars.dsl.translate import translate_ir +from cudf_polars.dsl.translate import translate_ir, Translator if TYPE_CHECKING: from collections.abc import Generator @@ -174,16 +174,22 @@ def execute_with_cudf( device = config.device memory_resource = config.memory_resource raise_on_fail = config.config.get("raise_on_fail", False) - if unsupported := (config.config.keys() - {"raise_on_fail"}): + debug_mode = config.config.get("debug_mode", False) + if unsupported := (config.config.keys() - {"raise_on_fail", "debug_mode"}): raise ValueError( f"Engine configuration contains unsupported settings {unsupported}" ) try: with nvtx.annotate(message="ConvertIR", domain="cudf_polars"): + translator = Translator(nt, debug_mode=debug_mode) + translation = translate_ir(translator) + if debug_mode: + print(set(translator.errors)) + raise NotImplementedError("Query contained unsupported operations") nt.set_udf( partial( _callback, - translate_ir(nt), + translation, device=device, memory_resource=memory_resource, ) diff --git a/python/cudf_polars/cudf_polars/dsl/translate.py b/python/cudf_polars/cudf_polars/dsl/translate.py index a0291037f01..4f9bde5bea4 100644 --- a/python/cudf_polars/cudf_polars/dsl/translate.py +++ b/python/cudf_polars/cudf_polars/dsl/translate.py @@ -25,6 +25,28 @@ __all__ = ["translate_ir", "translate_named_expr"] +class Translator: + def __init__(self, visitor: NodeTraverser, *, debug_mode: bool = False): + self.visitor = visitor + self.debug_mode = debug_mode + self.errors = [] + + +def debug(func): + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except Exception as e: + for arg in args: + if isinstance(arg, Translator): + if arg.debug_mode: + arg.errors.append(e) + return ir.ErrorNode(arg.visitor.get_schema(), e) + raise + + return wrapper + + class set_node(AbstractContextManager[None]): """ Run a block with current node set in the visitor. @@ -64,29 +86,36 @@ def __exit__(self, *args: Any) -> None: @singledispatch def _translate_ir( - node: Any, visitor: NodeTraverser, schema: dict[str, plc.DataType] + node: Any, translator: Translator, schema: dict[str, plc.DataType] ) -> ir.IR: - raise NotImplementedError( - f"Translation for {type(node).__name__}" - ) # pragma: no cover + visitor = translator.visitor + e = f"Translation for {type(node).__name__}" + if debug_mode: + translator.errors.append(e) + return ir.ErrorNode(schema, e) + raise NotImplementedError(e) # pragma: no cover +@debug @_translate_ir.register def _( - node: pl_ir.PythonScan, visitor: NodeTraverser, schema: dict[str, plc.DataType] + node: pl_ir.PythonScan, translator: Translator, schema: dict[str, plc.DataType] ) -> ir.IR: + visitor = translator.visitor scan_fn, with_columns, source_type, predicate, nrows = node.options options = (scan_fn, with_columns, source_type, nrows) predicate = ( - translate_named_expr(visitor, n=predicate) if predicate is not None else None + translate_named_expr(translator, n=predicate) if predicate is not None else None ) return ir.PythonScan(schema, options, predicate) +@debug @_translate_ir.register def _( - node: pl_ir.Scan, visitor: NodeTraverser, schema: dict[str, plc.DataType] + node: pl_ir.Scan, translator: Translator, schema: dict[str, plc.DataType] ) -> ir.IR: + visitor = translator.visitor typ, *options = node.scan_type if typ == "ndjson": (reader_options,) = map(json.loads, options) @@ -114,51 +143,59 @@ def _( skip_rows, n_rows, row_index, - translate_named_expr(visitor, n=node.predicate) + translate_named_expr(translator, n=node.predicate) if node.predicate is not None else None, ) +@debug @_translate_ir.register def _( - node: pl_ir.Cache, visitor: NodeTraverser, schema: dict[str, plc.DataType] + node: pl_ir.Cache, translator: Translator, schema: dict[str, plc.DataType] ) -> ir.IR: - return ir.Cache(schema, node.id_, translate_ir(visitor, n=node.input)) + visitor = translator.visitor + return ir.Cache(schema, node.id_, translate_ir(translator, n=node.input)) +@debug @_translate_ir.register def _( - node: pl_ir.DataFrameScan, visitor: NodeTraverser, schema: dict[str, plc.DataType] + node: pl_ir.DataFrameScan, translator: Translator, schema: dict[str, plc.DataType] ) -> ir.IR: + visitor = translator.visitor return ir.DataFrameScan( schema, node.df, node.projection, - translate_named_expr(visitor, n=node.selection) + translate_named_expr(translator, n=node.selection) if node.selection is not None else None, ) +@debug @_translate_ir.register def _( - node: pl_ir.Select, visitor: NodeTraverser, schema: dict[str, plc.DataType] + node: pl_ir.Select, translator: Translator, schema: dict[str, plc.DataType] ) -> ir.IR: + visitor = translator.visitor with set_node(visitor, node.input): - inp = translate_ir(visitor, n=None) - exprs = [translate_named_expr(visitor, n=e) for e in node.expr] + inp = translate_ir(translator, n=None) + exprs = [translate_named_expr(translator, n=e) for e in node.expr] return ir.Select(schema, inp, exprs, node.should_broadcast) +@debug @_translate_ir.register def _( - node: pl_ir.GroupBy, visitor: NodeTraverser, schema: dict[str, plc.DataType] + node: pl_ir.GroupBy, translator: Translator, schema: dict[str, plc.DataType] ) -> ir.IR: + visitor = translator.visitor with set_node(visitor, node.input): - inp = translate_ir(visitor, n=None) - aggs = [translate_named_expr(visitor, n=e) for e in node.aggs] - keys = [translate_named_expr(visitor, n=e) for e in node.keys] + inp = translate_ir(translator, n=None) + aggs = [translate_named_expr(translator, n=e) for e in node.aggs] + keys = [translate_named_expr(translator, n=e) for e in node.keys] return ir.GroupBy( schema, inp, @@ -169,120 +206,144 @@ def _( ) +@debug @_translate_ir.register def _( - node: pl_ir.Join, visitor: NodeTraverser, schema: dict[str, plc.DataType] + node: pl_ir.Join, translator: Translator, schema: dict[str, plc.DataType] ) -> ir.IR: + visitor = translator.visitor # Join key dtypes are dependent on the schema of the left and # right inputs, so these must be translated with the relevant # input active. with set_node(visitor, node.input_left): - inp_left = translate_ir(visitor, n=None) - left_on = [translate_named_expr(visitor, n=e) for e in node.left_on] + inp_left = translate_ir(translator, n=None) + left_on = [translate_named_expr(translator, n=e) for e in node.left_on] with set_node(visitor, node.input_right): - inp_right = translate_ir(visitor, n=None) - right_on = [translate_named_expr(visitor, n=e) for e in node.right_on] + inp_right = translate_ir(translator, n=None) + right_on = [translate_named_expr(translator, n=e) for e in node.right_on] return ir.Join(schema, inp_left, inp_right, left_on, right_on, node.options) +@debug @_translate_ir.register def _( - node: pl_ir.HStack, visitor: NodeTraverser, schema: dict[str, plc.DataType] + node: pl_ir.HStack, translator: Translator, schema: dict[str, plc.DataType] ) -> ir.IR: + visitor = translator.visitor with set_node(visitor, node.input): - inp = translate_ir(visitor, n=None) - exprs = [translate_named_expr(visitor, n=e) for e in node.exprs] + inp = translate_ir(translator, n=None) + exprs = [translate_named_expr(translator, n=e) for e in node.exprs] return ir.HStack(schema, inp, exprs, node.should_broadcast) +@debug @_translate_ir.register def _( - node: pl_ir.Reduce, visitor: NodeTraverser, schema: dict[str, plc.DataType] + node: pl_ir.Reduce, translator: Translator, schema: dict[str, plc.DataType] ) -> ir.IR: # pragma: no cover; polars doesn't emit this node yet + visitor = translator.visitor with set_node(visitor, node.input): - inp = translate_ir(visitor, n=None) - exprs = [translate_named_expr(visitor, n=e) for e in node.expr] + inp = translate_ir(translator, n=None) + exprs = [translate_named_expr(translator, n=e) for e in node.expr] return ir.Reduce(schema, inp, exprs) +@debug @_translate_ir.register def _( - node: pl_ir.Distinct, visitor: NodeTraverser, schema: dict[str, plc.DataType] + node: pl_ir.Distinct, translator: Translator, schema: dict[str, plc.DataType] ) -> ir.IR: + visitor = translator.visitor return ir.Distinct( schema, - translate_ir(visitor, n=node.input), + translate_ir(translator, n=node.input), node.options, ) +@debug @_translate_ir.register def _( - node: pl_ir.Sort, visitor: NodeTraverser, schema: dict[str, plc.DataType] + node: pl_ir.Sort, translator: Translator, schema: dict[str, plc.DataType] ) -> ir.IR: + visitor = translator.visitor with set_node(visitor, node.input): - inp = translate_ir(visitor, n=None) - by = [translate_named_expr(visitor, n=e) for e in node.by_column] + inp = translate_ir(translator, n=None) + by = [translate_named_expr(translator, n=e) for e in node.by_column] return ir.Sort(schema, inp, by, node.sort_options, node.slice) +@debug @_translate_ir.register def _( - node: pl_ir.Slice, visitor: NodeTraverser, schema: dict[str, plc.DataType] + node: pl_ir.Slice, translator: Translator, schema: dict[str, plc.DataType] ) -> ir.IR: - return ir.Slice(schema, translate_ir(visitor, n=node.input), node.offset, node.len) + visitor = translator.visitor + return ir.Slice( + schema, translate_ir(translator, n=node.input), node.offset, node.len + ) +@debug @_translate_ir.register def _( - node: pl_ir.Filter, visitor: NodeTraverser, schema: dict[str, plc.DataType] + node: pl_ir.Filter, translator: Translator, schema: dict[str, plc.DataType] ) -> ir.IR: + visitor = translator.visitor with set_node(visitor, node.input): - inp = translate_ir(visitor, n=None) - mask = translate_named_expr(visitor, n=node.predicate) + inp = translate_ir(translator, n=None) + mask = translate_named_expr(translator, n=node.predicate) return ir.Filter(schema, inp, mask) +@debug @_translate_ir.register def _( node: pl_ir.SimpleProjection, - visitor: NodeTraverser, + translator: Translator, schema: dict[str, plc.DataType], ) -> ir.IR: - return ir.Projection(schema, translate_ir(visitor, n=node.input)) + visitor = translator.visitor + return ir.Projection(schema, translate_ir(translator, n=node.input)) +@debug @_translate_ir.register def _( - node: pl_ir.MapFunction, visitor: NodeTraverser, schema: dict[str, plc.DataType] + node: pl_ir.MapFunction, translator: Translator, schema: dict[str, plc.DataType] ) -> ir.IR: name, *options = node.function + visitor = translator.visitor return ir.MapFunction( schema, # TODO: merge_sorted breaks this pattern - translate_ir(visitor, n=node.input), + translate_ir(translator, n=node.input), name, options, ) +@debug @_translate_ir.register def _( - node: pl_ir.Union, visitor: NodeTraverser, schema: dict[str, plc.DataType] + node: pl_ir.Union, translator: Translator, schema: dict[str, plc.DataType] ) -> ir.IR: + visitor = translator.visitor return ir.Union( - schema, [translate_ir(visitor, n=n) for n in node.inputs], node.options + schema, [translate_ir(translator, n=n) for n in node.inputs], node.options ) +@debug @_translate_ir.register def _( - node: pl_ir.HConcat, visitor: NodeTraverser, schema: dict[str, plc.DataType] + node: pl_ir.HConcat, translator: Translator, schema: dict[str, plc.DataType] ) -> ir.IR: - return ir.HConcat(schema, [translate_ir(visitor, n=n) for n in node.inputs]) + visitor = translator.visitor + return ir.HConcat(schema, [translate_ir(translator, n=n) for n in node.inputs]) -def translate_ir(visitor: NodeTraverser, *, n: int | None = None) -> ir.IR: +def translate_ir(translator: Translator, *, n: int | None = None) -> ir.IR: """ Translate a polars-internal IR node to our representation. @@ -293,6 +354,9 @@ def translate_ir(visitor: NodeTraverser, *, n: int | None = None) -> ir.IR: n Optional node to start traversing from, if not provided uses current polars-internal node. + debug_mode + Optional: If true returns an ErrorNode in the IR that is used to + report unsupported operations in the query Returns ------- @@ -303,6 +367,7 @@ def translate_ir(visitor: NodeTraverser, *, n: int | None = None) -> ir.IR: NotImplementedError If we can't translate the nodes due to unsupported functionality. """ + visitor = translator.visitor ctx: AbstractContextManager[None] = ( set_node(visitor, n) if n is not None else noop_context ) @@ -319,19 +384,21 @@ def translate_ir(visitor: NodeTraverser, *, n: int | None = None) -> ir.IR: polars_schema = visitor.get_schema() node = visitor.view_current_node() schema = {k: dtypes.from_polars(v) for k, v in polars_schema.items()} - result = _translate_ir(node, visitor, schema) + result = _translate_ir(node, translator, schema) if any( isinstance(dtype, pl.Null) for dtype in pl.datatypes.unpack_dtypes(*polars_schema.values()) ): - raise NotImplementedError( - f"No GPU support for {result} with Null column dtype." - ) + e = f"No GPU support for {result} with Null column dtype." + if debug_mode: + translator.errors.append(e) + return ir.ErrorNode(schema, e) + raise NotImplementedError(e) return result def translate_named_expr( - visitor: NodeTraverser, *, n: pl_expr.PyExprIR + translator: Translator, *, n: pl_expr.PyExprIR ) -> expr.NamedExpr: """ Translate a polars-internal named expression IR object into our representation. @@ -359,20 +426,25 @@ def translate_named_expr( NotImplementedError If any translation fails due to unsupported functionality. """ - return expr.NamedExpr(n.output_name, translate_expr(visitor, n=n.node)) + visitor = translator.visitor + return expr.NamedExpr(n.output_name, translate_expr(translator, n=n.node)) @singledispatch def _translate_expr( - node: Any, visitor: NodeTraverser, dtype: plc.DataType + node: Any, translator: Translator, dtype: plc.DataType ) -> expr.Expr: - raise NotImplementedError( - f"Translation for {type(node).__name__}" - ) # pragma: no cover + e = f"Translation for {type(node).__name__}" + if debug_mode: + translator.errors.append(e) + return ir.ErrorNode(schema, e) + raise NotImplementedError(e) # pragma: no cover +@debug @_translate_expr.register -def _(node: pl_expr.Function, visitor: NodeTraverser, dtype: plc.DataType) -> expr.Expr: +def _(node: pl_expr.Function, translator: Translator, dtype: plc.DataType) -> expr.Expr: + visitor = translator.visitor name, *options = node.function_data options = tuple(options) if isinstance(name, pl_expr.StringFunction): @@ -381,7 +453,7 @@ def _(node: pl_expr.Function, visitor: NodeTraverser, dtype: plc.DataType) -> ex pl_expr.StringFunction.StripCharsStart, pl_expr.StringFunction.StripCharsEnd, }: - column, chars = (translate_expr(visitor, n=n) for n in node.input) + column, chars = (translate_expr(translator, n=n) for n in node.input) if isinstance(chars, expr.Literal): if chars.value == pa.scalar(""): # No-op in polars, but libcudf uses empty string @@ -398,11 +470,11 @@ def _(node: pl_expr.Function, visitor: NodeTraverser, dtype: plc.DataType) -> ex dtype, name, options, - *(translate_expr(visitor, n=n) for n in node.input), + *(translate_expr(translator, n=n) for n in node.input), ) elif isinstance(name, pl_expr.BooleanFunction): if name == pl_expr.BooleanFunction.IsBetween: - column, lo, hi = (translate_expr(visitor, n=n) for n in node.input) + column, lo, hi = (translate_expr(translator, n=n) for n in node.input) (closed,) = options lop, rop = expr.BooleanFunction._BETWEEN_OPS[closed] return expr.BinOp( @@ -415,7 +487,7 @@ def _(node: pl_expr.Function, visitor: NodeTraverser, dtype: plc.DataType) -> ex dtype, name, options, - *(translate_expr(visitor, n=n) for n in node.input), + *(translate_expr(translator, n=n) for n in node.input), ) elif isinstance(name, pl_expr.TemporalFunction): # functions for which evaluation of the expression may not return @@ -435,14 +507,14 @@ def _(node: pl_expr.Function, visitor: NodeTraverser, dtype: plc.DataType) -> ex dtype, name, options, - *(translate_expr(visitor, n=n) for n in node.input), + *(translate_expr(translator, n=n) for n in node.input), ) if name in needs_cast: return expr.Cast(dtype, result_expr) return result_expr elif isinstance(name, str): - children = (translate_expr(visitor, n=n) for n in node.input) + children = (translate_expr(translator, n=n) for n in node.input) if name == "log": (base,) = options (child,) = children @@ -461,69 +533,85 @@ def _(node: pl_expr.Function, visitor: NodeTraverser, dtype: plc.DataType) -> ex @_translate_expr.register -def _(node: pl_expr.Window, visitor: NodeTraverser, dtype: plc.DataType) -> expr.Expr: - # TODO: raise in groupby? - if isinstance(node.options, pl_expr.RollingGroupOptions): - # pl.col("a").rolling(...) - return expr.RollingWindow( - dtype, node.options, translate_expr(visitor, n=node.function) - ) - elif isinstance(node.options, pl_expr.WindowMapping): - # pl.col("a").over(...) - return expr.GroupedRollingWindow( - dtype, - node.options, - translate_expr(visitor, n=node.function), - *(translate_expr(visitor, n=n) for n in node.partition_by), - ) - assert_never(node.options) +def _(node: pl_expr.Window, translator: Translator, dtype: plc.DataType) -> expr.Expr: + visitor = translator.visitor + try: + # TODO: raise in groupby? + if isinstance(node.options, pl_expr.RollingGroupOptions): + # pl.col("a").rolling(...) + return expr.RollingWindow( + dtype, node.options, translate_expr(translator, n=node.function) + ) + elif isinstance(node.options, pl_expr.WindowMapping): + # pl.col("a").over(...) + return expr.GroupedRollingWindow( + dtype, + node.options, + translate_expr(translator, n=node.function), + *(translate_expr(translator, n=n) for n in node.partition_by), + ) + assert_never(node.options) + except Exception as e: + translator.errors.append(e) +@debug @_translate_expr.register -def _(node: pl_expr.Literal, visitor: NodeTraverser, dtype: plc.DataType) -> expr.Expr: +def _(node: pl_expr.Literal, translator: Translator, dtype: plc.DataType) -> expr.Expr: + visitor = translator.visitor if isinstance(node.value, plrs.PySeries): return expr.LiteralColumn(dtype, pl.Series._from_pyseries(node.value)) value = pa.scalar(node.value, type=plc.interop.to_arrow(dtype)) return expr.Literal(dtype, value) +@debug @_translate_expr.register -def _(node: pl_expr.Sort, visitor: NodeTraverser, dtype: plc.DataType) -> expr.Expr: +def _(node: pl_expr.Sort, translator: Translator, dtype: plc.DataType) -> expr.Expr: + visitor = translator.visitor # TODO: raise in groupby - return expr.Sort(dtype, node.options, translate_expr(visitor, n=node.expr)) + return expr.Sort(dtype, node.options, translate_expr(translator, n=node.expr)) +@debug @_translate_expr.register -def _(node: pl_expr.SortBy, visitor: NodeTraverser, dtype: plc.DataType) -> expr.Expr: +def _(node: pl_expr.SortBy, translator: Translator, dtype: plc.DataType) -> expr.Expr: + visitor = translator.visitor return expr.SortBy( dtype, node.sort_options, - translate_expr(visitor, n=node.expr), - *(translate_expr(visitor, n=n) for n in node.by), + translate_expr(translator, n=node.expr), + *(translate_expr(translator, n=n) for n in node.by), ) +@debug @_translate_expr.register -def _(node: pl_expr.Gather, visitor: NodeTraverser, dtype: plc.DataType) -> expr.Expr: +def _(node: pl_expr.Gather, translator: Translator, dtype: plc.DataType) -> expr.Expr: + visitor = translator.visitor return expr.Gather( dtype, - translate_expr(visitor, n=node.expr), - translate_expr(visitor, n=node.idx), + translate_expr(translator, n=node.expr), + translate_expr(translator, n=node.idx), ) +@debug @_translate_expr.register -def _(node: pl_expr.Filter, visitor: NodeTraverser, dtype: plc.DataType) -> expr.Expr: +def _(node: pl_expr.Filter, translator: Translator, dtype: plc.DataType) -> expr.Expr: + visitor = translator.visitor return expr.Filter( dtype, - translate_expr(visitor, n=node.input), - translate_expr(visitor, n=node.by), + translate_expr(translator, n=node.input), + translate_expr(translator, n=node.by), ) +@debug @_translate_expr.register -def _(node: pl_expr.Cast, visitor: NodeTraverser, dtype: plc.DataType) -> expr.Expr: - inner = translate_expr(visitor, n=node.expr) +def _(node: pl_expr.Cast, translator: Translator, dtype: plc.DataType) -> expr.Expr: + visitor = translator.visitor + inner = translate_expr(translator, n=node.expr) # Push casts into literals so we can handle Cast(Literal(Null)) if isinstance(inner, expr.Literal): return expr.Literal(dtype, inner.value.cast(plc.interop.to_arrow(dtype))) @@ -534,55 +622,65 @@ def _(node: pl_expr.Cast, visitor: NodeTraverser, dtype: plc.DataType) -> expr.E return expr.Cast(dtype, inner) +@debug @_translate_expr.register -def _(node: pl_expr.Column, visitor: NodeTraverser, dtype: plc.DataType) -> expr.Expr: +def _(node: pl_expr.Column, translator: Translator, dtype: plc.DataType) -> expr.Expr: + visitor = translator.visitor return expr.Col(dtype, node.name) +@debug @_translate_expr.register -def _(node: pl_expr.Agg, visitor: NodeTraverser, dtype: plc.DataType) -> expr.Expr: +def _(node: pl_expr.Agg, translator: Translator, dtype: plc.DataType) -> expr.Expr: + visitor = translator.visitor value = expr.Agg( dtype, node.name, node.options, - *(translate_expr(visitor, n=n) for n in node.arguments), + *(translate_expr(translator, n=n) for n in node.arguments), ) if value.name == "count" and value.dtype.id() != plc.TypeId.INT32: return expr.Cast(value.dtype, value) return value +@debug @_translate_expr.register -def _(node: pl_expr.Ternary, visitor: NodeTraverser, dtype: plc.DataType) -> expr.Expr: +def _(node: pl_expr.Ternary, translator: Translator, dtype: plc.DataType) -> expr.Expr: + visitor = translator.visitor return expr.Ternary( dtype, - translate_expr(visitor, n=node.predicate), - translate_expr(visitor, n=node.truthy), - translate_expr(visitor, n=node.falsy), + translate_expr(translator, n=node.predicate), + translate_expr(translator, n=node.truthy), + translate_expr(translator, n=node.falsy), ) +@debug @_translate_expr.register def _( - node: pl_expr.BinaryExpr, visitor: NodeTraverser, dtype: plc.DataType + node: pl_expr.BinaryExpr, translator: Translator, dtype: plc.DataType ) -> expr.Expr: + visitor = translator.visitor return expr.BinOp( dtype, expr.BinOp._MAPPING[node.op], - translate_expr(visitor, n=node.left), - translate_expr(visitor, n=node.right), + translate_expr(translator, n=node.left), + translate_expr(translator, n=node.right), ) +@debug @_translate_expr.register -def _(node: pl_expr.Len, visitor: NodeTraverser, dtype: plc.DataType) -> expr.Expr: +def _(node: pl_expr.Len, translator: Translator, dtype: plc.DataType) -> expr.Expr: + visitor = translator.visitor value = expr.Len(dtype) if dtype.id() != plc.TypeId.INT32: return expr.Cast(dtype, value) return value # pragma: no cover; never reached since polars len has uint32 dtype -def translate_expr(visitor: NodeTraverser, *, n: int) -> expr.Expr: +def translate_expr(translator: Translator, *, n: int) -> expr.Expr: """ Translate a polars-internal expression IR into our representation. @@ -602,6 +700,7 @@ def translate_expr(visitor: NodeTraverser, *, n: int) -> expr.Expr: NotImplementedError If any translation fails due to unsupported functionality. """ + visitor = translator.visitor node = visitor.view_expression(n) dtype = dtypes.from_polars(visitor.get_dtype(n)) - return _translate_expr(node, visitor, dtype) + return _translate_expr(node, translator, dtype)