From 221f454b3ec48f192c3a16ac394d13ccc2853235 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Wed, 3 Sep 2025 12:12:55 +0000 Subject: [PATCH 1/5] feat(expr-ir): Check eq for all replace `**changes` Part of https://github.com/narwhals-dev/narwhals/pull/3066#issuecomment-3242037939 --- narwhals/_plan/_immutable.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/narwhals/_plan/_immutable.py b/narwhals/_plan/_immutable.py index 248e1b5631..0abe0739b6 100644 --- a/narwhals/_plan/_immutable.py +++ b/narwhals/_plan/_immutable.py @@ -84,17 +84,17 @@ def __setattr__(self, name: str, value: Never) -> Never: def __replace__(self, **changes: Any) -> Self: """https://docs.python.org/3.13/library/copy.html#copy.replace""" # noqa: D415 if len(changes) == 1: - k_new, v_new = next(iter(changes.items())) - # NOTE: Will trigger an attribute error if invalid name - if getattr(self, k_new) == v_new: + # The most common case is a single field replacement. + # Iff that field happens to be equal, we can noop, preserving the current object's hash. + name, value_changed = next(iter(changes.items())) + if getattr(self, name) == value_changed: return self - changed = dict(self.__immutable_items__) - # Now we *don't* need to check the key is valid - changed[k_new] = v_new + changes = dict(self.__immutable_items__, **changes) else: - changed = dict(self.__immutable_items__) - changed |= changes - return type(self)(**changed) + for name, value_current in self.__immutable_items__: + if name not in changes or value_current == changes[name]: + changes[name] = value_current + return type(self)(**changes) def __init_subclass__(cls, *args: Any, **kwds: Any) -> None: super().__init_subclass__(*args, **kwds) From fa495478ff7152162d68e46c50a4f7b22fa5fd4e Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Wed, 3 Sep 2025 13:00:05 +0000 Subject: [PATCH 2/5] refactor: Remove most `with_*` methods --- narwhals/_plan/aggregation.py | 7 +-- narwhals/_plan/common.py | 7 +-- narwhals/_plan/expr.py | 88 +++++++++----------------------- narwhals/_plan/expr_expansion.py | 5 +- narwhals/_plan/expr_rewrites.py | 5 +- narwhals/_plan/name.py | 12 +---- 6 files changed, 33 insertions(+), 91 deletions(-) diff --git a/narwhals/_plan/aggregation.py b/narwhals/_plan/aggregation.py index 66f58e7264..f230309ec1 100644 --- a/narwhals/_plan/aggregation.py +++ b/narwhals/_plan/aggregation.py @@ -8,8 +8,6 @@ if TYPE_CHECKING: from collections.abc import Iterator - from typing_extensions import Self - from narwhals._plan.typing import MapIR from narwhals.typing import RollingInterpolationMethod @@ -29,10 +27,7 @@ def iter_output_name(self) -> Iterator[ExprIR]: yield from self.expr.iter_output_name() def map_ir(self, function: MapIR, /) -> ExprIR: - return function(self.with_expr(self.expr.map_ir(function))) - - def with_expr(self, expr: ExprIR, /) -> Self: - return replace(self, expr=expr) + return function(replace(self, expr=self.expr.map_ir(function))) def __init__(self, *, expr: ExprIR, **kwds: Any) -> None: if expr.is_scalar: diff --git a/narwhals/_plan/common.py b/narwhals/_plan/common.py index 6b6382496d..1aee0d054b 100644 --- a/narwhals/_plan/common.py +++ b/narwhals/_plan/common.py @@ -338,12 +338,9 @@ def from_ir(expr: ExprIRT2, /) -> NamedIR[ExprIRT2]: """ return NamedIR(expr=expr, name=expr.meta.output_name(raise_if_undetermined=True)) - def map_ir(self, function: MapIR, /) -> NamedIR[ExprIR]: + def map_ir(self, function: MapIR, /) -> Self: """**WARNING**: don't use renaming ops here, or `self.name` is invalid.""" - return self.with_expr(function(self.expr.map_ir(function))) - - def with_expr(self, expr: ExprIRT2, /) -> NamedIR[ExprIRT2]: - return cast("NamedIR[ExprIRT2]", replace(self, expr=expr)) + return replace(self, expr=function(self.expr.map_ir(function))) def __repr__(self) -> str: return f"{self.name}={self.expr!r}" diff --git a/narwhals/_plan/expr.py b/narwhals/_plan/expr.py index ef120c2830..8781ee4e4b 100644 --- a/narwhals/_plan/expr.py +++ b/narwhals/_plan/expr.py @@ -16,14 +16,12 @@ FunctionT, LeftSelectorT, LeftT, - LeftT2, LiteralT, MapIR, OperatorT, RangeT, RightSelectorT, RightT, - RightT2, RollingT, SelectorOperatorT, SelectorT, @@ -103,10 +101,7 @@ def __repr__(self) -> str: return f"{self.expr!r}.alias({self.name!r})" def map_ir(self, function: MapIR, /) -> ExprIR: - return function(self.with_expr(self.expr.map_ir(function))) - - def with_expr(self, expr: ExprIR, /) -> Self: - return common.replace(self, expr=expr) + return function(common.replace(self, expr=self.expr.map_ir(function))) class Column(ExprIR, config=ExprIROptions.namespaced("col")): @@ -116,9 +111,6 @@ class Column(ExprIR, config=ExprIROptions.namespaced("col")): def __repr__(self) -> str: return f"col({self.name!r})" - def with_name(self, name: str, /) -> Column: - return common.replace(self, name=name) - class _ColumnSelection(ExprIR, config=ExprIROptions.no_dispatch()): """Nodes which can resolve to `Column`(s) with a `Schema`.""" @@ -169,10 +161,7 @@ def __repr__(self) -> str: return f"{self.expr!r}.exclude({list(self.names)!r})" def map_ir(self, function: MapIR, /) -> ExprIR: - return function(self.with_expr(self.expr.map_ir(function))) - - def with_expr(self, expr: ExprIR, /) -> Self: - return common.replace(self, expr=expr) + return function(common.replace(self, expr=self.expr.map_ir(function))) class Literal(ExprIR, t.Generic[LiteralT], config=ExprIROptions.namespaced("lit")): @@ -224,20 +213,10 @@ class BinaryExpr( def iter_output_name(self) -> t.Iterator[ExprIR]: yield from self.left.iter_output_name() - def with_left(self, left: LeftT2, /) -> BinaryExpr[LeftT2, OperatorT, RightT]: - changed = common.replace(self, left=left) - return t.cast("BinaryExpr[LeftT2, OperatorT, RightT]", changed) - - def with_right(self, right: RightT2, /) -> BinaryExpr[LeftT, OperatorT, RightT2]: - changed = common.replace(self, right=right) - return t.cast("BinaryExpr[LeftT, OperatorT, RightT2]", changed) - def map_ir(self, function: MapIR, /) -> ExprIR: - return function( - self.with_left(self.left.map_ir(function)).with_right( - self.right.map_ir(function) - ) - ) + left = self.left.map_ir(function) + right = self.right.map_ir(function) + return function(common.replace(self, left=left, right=right)) class Cast(ExprIR, child=("expr",)): @@ -256,10 +235,7 @@ def iter_output_name(self) -> t.Iterator[ExprIR]: yield from self.expr.iter_output_name() def map_ir(self, function: MapIR, /) -> ExprIR: - return function(self.with_expr(self.expr.map_ir(function))) - - def with_expr(self, expr: ExprIR, /) -> Self: - return common.replace(self, expr=expr) + return function(common.replace(self, expr=self.expr.map_ir(function))) class Sort(ExprIR, child=("expr",)): @@ -279,10 +255,7 @@ def iter_output_name(self) -> t.Iterator[ExprIR]: yield from self.expr.iter_output_name() def map_ir(self, function: MapIR, /) -> ExprIR: - return function(self.with_expr(self.expr.map_ir(function))) - - def with_expr(self, expr: ExprIR, /) -> Self: - return common.replace(self, expr=expr) + return function(common.replace(self, expr=self.expr.map_ir(function))) class SortBy(ExprIR, child=("expr", "by")): @@ -304,14 +277,9 @@ def iter_output_name(self) -> t.Iterator[ExprIR]: yield from self.expr.iter_output_name() def map_ir(self, function: MapIR, /) -> ExprIR: - by = (ir.map_ir(function) for ir in self.by) - return function(self.with_expr(self.expr.map_ir(function)).with_by(by)) - - def with_expr(self, expr: ExprIR, /) -> Self: - return common.replace(self, expr=expr) - - def with_by(self, by: t.Iterable[ExprIR], /) -> Self: - return common.replace(self, by=collect(by)) + expr = self.expr.map_ir(function) + by = tuple(ir.map_ir(function) for ir in self.by) + return function(common.replace(self, expr=expr, by=by)) class FunctionExpr(ExprIR, t.Generic[FunctionT], child=("input",)): @@ -336,11 +304,9 @@ def is_scalar(self) -> bool: def with_options(self, options: FunctionOptions, /) -> Self: return common.replace(self, options=self.options.with_flags(options.flags)) - def with_input(self, input: t.Iterable[ExprIR], /) -> Self: # noqa: A002 - return common.replace(self, input=collect(input)) - def map_ir(self, function: MapIR, /) -> ExprIR: - return function(self.with_input(ir.map_ir(function) for ir in self.input)) + input = tuple(ir.map_ir(function) for ir in self.input) + return function(common.replace(self, input=input)) def __repr__(self) -> str: if self.input: @@ -443,9 +409,9 @@ def iter_output_name(self) -> t.Iterator[ExprIR]: yield from self.expr.iter_output_name() def map_ir(self, function: MapIR, /) -> ExprIR: - expr, by = self.expr, self.by - changed = common.replace(self, expr=expr.map_ir(function), by=by.map_ir(function)) - return function(changed) + expr = self.expr.map_ir(function) + by = self.by.map_ir(function) + return function(common.replace(self, expr=expr, by=by)) class WindowExpr( @@ -472,16 +438,9 @@ def iter_output_name(self) -> t.Iterator[ExprIR]: yield from self.expr.iter_output_name() def map_ir(self, function: MapIR, /) -> ExprIR: - over = self.with_expr(self.expr.map_ir(function)).with_partition_by( - ir.map_ir(function) for ir in self.partition_by - ) - return function(over) - - def with_expr(self, expr: ExprIR, /) -> Self: - return common.replace(self, expr=expr) - - def with_partition_by(self, partition_by: t.Iterable[ExprIR], /) -> Self: - return common.replace(self, partition_by=collect(partition_by)) + expr = self.expr.map_ir(function) + partition_by = tuple(ir.map_ir(function) for ir in self.partition_by) + return function(common.replace(self, expr=expr, partition_by=partition_by)) class OrderedWindowExpr( @@ -515,15 +474,14 @@ def iter_root_names(self) -> t.Iterator[ExprIR]: yield self def map_ir(self, function: MapIR, /) -> ExprIR: - over = self.with_expr(self.expr.map_ir(function)).with_partition_by( - ir.map_ir(function) for ir in self.partition_by + expr = self.expr.map_ir(function) + partition_by = tuple(ir.map_ir(function) for ir in self.partition_by) + order_by = tuple(ir.map_ir(function) for ir in self.order_by) + over = common.replace( + self, expr=expr, partition_by=partition_by, order_by=order_by ) - over = over.with_order_by(ir.map_ir(function) for ir in self.order_by) return function(over) - def with_order_by(self, order_by: t.Iterable[ExprIR], /) -> Self: - return common.replace(self, order_by=collect(order_by)) - class Len(ExprIR, config=ExprIROptions.namespaced()): @property diff --git a/narwhals/_plan/expr_expansion.py b/narwhals/_plan/expr_expansion.py index 86073ef074..463e8cdc7b 100644 --- a/narwhals/_plan/expr_expansion.py +++ b/narwhals/_plan/expr_expansion.py @@ -212,9 +212,8 @@ def _root_names_unique(exprs: Seq[ExprIR]) -> set[str]: def expand_function_inputs(origin: ExprIR, /, *, schema: FrozenSchema) -> ExprIR: def fn(child: ExprIR, /) -> ExprIR: if is_horizontal_reduction(child): - return child.with_input( - rewrite_projections(child.input, keys=(), schema=schema) - ) + rewrites = rewrite_projections(child.input, keys=(), schema=schema) + return common.replace(child, input=rewrites) return child return origin.map_ir(fn) diff --git a/narwhals/_plan/expr_rewrites.py b/narwhals/_plan/expr_rewrites.py index 705dec3371..875674aa94 100644 --- a/narwhals/_plan/expr_rewrites.py +++ b/narwhals/_plan/expr_rewrites.py @@ -12,6 +12,7 @@ is_function_expr, is_window_expr, map_ir, + replace, ) from narwhals._plan.expr_expansion import into_named_irs, prepare_projection @@ -59,7 +60,7 @@ def rewrite_elementwise_over(window: ExprIR, /) -> ExprIR: ): func = window.expr parent, *args = func.input - return func.with_input((window.with_expr(parent), *args)) + return replace(func, input=(replace(window, expr=parent), *args)) return window @@ -85,5 +86,5 @@ def rewrite_binary_agg_over(window: ExprIR, /) -> ExprIR: ): binary_expr = window.expr rhs = window.expr.right - return binary_expr.with_right(window.with_expr(rhs)) + return replace(binary_expr, right=replace(window, expr=rhs)) return window diff --git a/narwhals/_plan/name.py b/narwhals/_plan/name.py index b722a2c1da..7396500459 100644 --- a/narwhals/_plan/name.py +++ b/narwhals/_plan/name.py @@ -8,8 +8,6 @@ from narwhals._plan.options import ExprIROptions if TYPE_CHECKING: - from typing_extensions import Self - from narwhals._compliant.typing import AliasName from narwhals._plan.dummy import Expr from narwhals._plan.typing import MapIR @@ -27,10 +25,7 @@ def __repr__(self) -> str: return f"{self.expr!r}.name.keep()" def map_ir(self, function: MapIR, /) -> ExprIR: - return function(self.with_expr(self.expr.map_ir(function))) - - def with_expr(self, expr: ExprIR, /) -> Self: - return common.replace(self, expr=expr) + return function(common.replace(self, expr=self.expr.map_ir(function))) class RenameAlias(ExprIR, child=("expr",), config=ExprIROptions.no_dispatch()): @@ -46,10 +41,7 @@ def __repr__(self) -> str: return f".rename_alias({self.expr!r})" def map_ir(self, function: MapIR, /) -> ExprIR: - return function(self.with_expr(self.expr.map_ir(function))) - - def with_expr(self, expr: ExprIR, /) -> Self: - return common.replace(self, expr=expr) + return function(common.replace(self, expr=self.expr.map_ir(function))) class Prefix(Immutable): From 890f2c009f0373b8b9a8c38ef3c8a248d1c602f9 Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Wed, 3 Sep 2025 13:38:45 +0000 Subject: [PATCH 3/5] refactor: remove now-unused types --- narwhals/_plan/typing.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/narwhals/_plan/typing.py b/narwhals/_plan/typing.py index 0b8b884b5f..19df0ca087 100644 --- a/narwhals/_plan/typing.py +++ b/narwhals/_plan/typing.py @@ -47,10 +47,8 @@ RollingT = TypeVar("RollingT", bound="RollingWindow", default="RollingWindow") RangeT = TypeVar("RangeT", bound="RangeFunction", default="RangeFunction") LeftT = TypeVar("LeftT", bound="ExprIR", default="ExprIR") -LeftT2 = TypeVar("LeftT2", bound="ExprIR", default="ExprIR") OperatorT = TypeVar("OperatorT", bound="ops.Operator", default="ops.Operator") RightT = TypeVar("RightT", bound="ExprIR", default="ExprIR") -RightT2 = TypeVar("RightT2", bound="ExprIR", default="ExprIR") OperatorFn: TypeAlias = "t.Callable[[t.Any, t.Any], t.Any]" ExprIRT = TypeVar("ExprIRT", bound="ExprIR", default="ExprIR") ExprIRT2 = TypeVar("ExprIRT2", bound="ExprIR", default="ExprIR") From 0400974311dfbb6c50d633deacfc15f79500786b Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Wed, 3 Sep 2025 14:18:51 +0000 Subject: [PATCH 4/5] refactor: Implement singular `ExprIR.map_ir` --- narwhals/_plan/aggregation.py | 6 +--- narwhals/_plan/common.py | 10 ++++++- narwhals/_plan/expr.py | 53 ----------------------------------- narwhals/_plan/name.py | 16 +++-------- 4 files changed, 14 insertions(+), 71 deletions(-) diff --git a/narwhals/_plan/aggregation.py b/narwhals/_plan/aggregation.py index f230309ec1..b1f47ca1d7 100644 --- a/narwhals/_plan/aggregation.py +++ b/narwhals/_plan/aggregation.py @@ -2,13 +2,12 @@ from typing import TYPE_CHECKING, Any -from narwhals._plan.common import ExprIR, _pascal_to_snake_case, replace +from narwhals._plan.common import ExprIR, _pascal_to_snake_case from narwhals._plan.exceptions import agg_scalar_error if TYPE_CHECKING: from collections.abc import Iterator - from narwhals._plan.typing import MapIR from narwhals.typing import RollingInterpolationMethod @@ -26,9 +25,6 @@ def __repr__(self) -> str: def iter_output_name(self) -> Iterator[ExprIR]: yield from self.expr.iter_output_name() - def map_ir(self, function: MapIR, /) -> ExprIR: - return function(replace(self, expr=self.expr.map_ir(function))) - def __init__(self, *, expr: ExprIR, **kwds: Any) -> None: if expr.is_scalar: raise agg_scalar_error(self, expr) diff --git a/narwhals/_plan/common.py b/narwhals/_plan/common.py index 1aee0d054b..95c297bcb2 100644 --- a/narwhals/_plan/common.py +++ b/narwhals/_plan/common.py @@ -180,7 +180,11 @@ def map_ir(self, function: MapIR, /) -> ExprIR: [`polars_plan::plans::iterator::Expr.map_expr`]: https://github.com/pola-rs/polars/blob/0fa7141ce718c6f0a4d6ae46865c867b177a59ed/crates/polars-plan/src/plans/iterator.rs#L152-L159 [`polars_plan::plans::visitor::visitors`]: https://github.com/pola-rs/polars/blob/0fa7141ce718c6f0a4d6ae46865c867b177a59ed/crates/polars-plan/src/plans/visitor/visitors.rs """ - return function(self) + if not self._child: + return function(self) + children = ((name, getattr(self, name)) for name in self._child) + changed = {name: _map_ir_child(child, function) for name, child in children} + return function(replace(self, **changed)) def iter_left(self) -> Iterator[ExprIR]: """Yield nodes root->leaf. @@ -587,6 +591,10 @@ def map_ir( return origin.map_ir(function) +def _map_ir_child(obj: ExprIR | Seq[ExprIR], fn: MapIR, /) -> ExprIR | Seq[ExprIR]: + return obj.map_ir(fn) if isinstance(obj, ExprIR) else tuple(e.map_ir(fn) for e in obj) + + # TODO @dangotbanned: Review again and try to work around (https://github.com/microsoft/pyright/issues/10673#issuecomment-3033789021) # The issue is `T` possibly being `Iterable` # Ignoring here still leaks the issue to the caller, where you need to annotate the base case diff --git a/narwhals/_plan/expr.py b/narwhals/_plan/expr.py index 8781ee4e4b..c7067a64e8 100644 --- a/narwhals/_plan/expr.py +++ b/narwhals/_plan/expr.py @@ -17,7 +17,6 @@ LeftSelectorT, LeftT, LiteralT, - MapIR, OperatorT, RangeT, RightSelectorT, @@ -100,9 +99,6 @@ def is_scalar(self) -> bool: def __repr__(self) -> str: return f"{self.expr!r}.alias({self.name!r})" - def map_ir(self, function: MapIR, /) -> ExprIR: - return function(common.replace(self, expr=self.expr.map_ir(function))) - class Column(ExprIR, config=ExprIROptions.namespaced("col")): __slots__ = ("name",) @@ -160,9 +156,6 @@ def from_names(expr: ExprIR, *names: str | t.Iterable[str]) -> Exclude: def __repr__(self) -> str: return f"{self.expr!r}.exclude({list(self.names)!r})" - def map_ir(self, function: MapIR, /) -> ExprIR: - return function(common.replace(self, expr=self.expr.map_ir(function))) - class Literal(ExprIR, t.Generic[LiteralT], config=ExprIROptions.namespaced("lit")): """https://github.com/pola-rs/polars/blob/dafd0a2d0e32b52bcfa4273bffdd6071a0d5977a/crates/polars-plan/src/dsl/expr.rs#L81.""" @@ -213,11 +206,6 @@ class BinaryExpr( def iter_output_name(self) -> t.Iterator[ExprIR]: yield from self.left.iter_output_name() - def map_ir(self, function: MapIR, /) -> ExprIR: - left = self.left.map_ir(function) - right = self.right.map_ir(function) - return function(common.replace(self, left=left, right=right)) - class Cast(ExprIR, child=("expr",)): __slots__ = ("expr", "dtype") # noqa: RUF023 @@ -234,9 +222,6 @@ def __repr__(self) -> str: def iter_output_name(self) -> t.Iterator[ExprIR]: yield from self.expr.iter_output_name() - def map_ir(self, function: MapIR, /) -> ExprIR: - return function(common.replace(self, expr=self.expr.map_ir(function))) - class Sort(ExprIR, child=("expr",)): __slots__ = ("expr", "options") @@ -254,9 +239,6 @@ def __repr__(self) -> str: def iter_output_name(self) -> t.Iterator[ExprIR]: yield from self.expr.iter_output_name() - def map_ir(self, function: MapIR, /) -> ExprIR: - return function(common.replace(self, expr=self.expr.map_ir(function))) - class SortBy(ExprIR, child=("expr", "by")): """https://github.com/narwhals-dev/narwhals/issues/2534.""" @@ -276,11 +258,6 @@ def __repr__(self) -> str: def iter_output_name(self) -> t.Iterator[ExprIR]: yield from self.expr.iter_output_name() - def map_ir(self, function: MapIR, /) -> ExprIR: - expr = self.expr.map_ir(function) - by = tuple(ir.map_ir(function) for ir in self.by) - return function(common.replace(self, expr=expr, by=by)) - class FunctionExpr(ExprIR, t.Generic[FunctionT], child=("input",)): """**Representing `Expr::Function`**. @@ -304,10 +281,6 @@ def is_scalar(self) -> bool: def with_options(self, options: FunctionOptions, /) -> Self: return common.replace(self, options=self.options.with_flags(options.flags)) - def map_ir(self, function: MapIR, /) -> ExprIR: - input = tuple(ir.map_ir(function) for ir in self.input) - return function(common.replace(self, input=input)) - def __repr__(self) -> str: if self.input: first = self.input[0] @@ -408,11 +381,6 @@ def __repr__(self) -> str: def iter_output_name(self) -> t.Iterator[ExprIR]: yield from self.expr.iter_output_name() - def map_ir(self, function: MapIR, /) -> ExprIR: - expr = self.expr.map_ir(function) - by = self.by.map_ir(function) - return function(common.replace(self, expr=expr, by=by)) - class WindowExpr( ExprIR, child=("expr", "partition_by"), config=ExprIROptions.renamed("over") @@ -437,11 +405,6 @@ def __repr__(self) -> str: def iter_output_name(self) -> t.Iterator[ExprIR]: yield from self.expr.iter_output_name() - def map_ir(self, function: MapIR, /) -> ExprIR: - expr = self.expr.map_ir(function) - partition_by = tuple(ir.map_ir(function) for ir in self.partition_by) - return function(common.replace(self, expr=expr, partition_by=partition_by)) - class OrderedWindowExpr( WindowExpr, @@ -473,15 +436,6 @@ def iter_root_names(self) -> t.Iterator[ExprIR]: yield from e.iter_left() yield self - def map_ir(self, function: MapIR, /) -> ExprIR: - expr = self.expr.map_ir(function) - partition_by = tuple(ir.map_ir(function) for ir in self.partition_by) - order_by = tuple(ir.map_ir(function) for ir in self.order_by) - over = common.replace( - self, expr=expr, partition_by=partition_by, order_by=order_by - ) - return function(over) - class Len(ExprIR, config=ExprIROptions.namespaced()): @property @@ -552,10 +506,3 @@ def __repr__(self) -> str: def iter_output_name(self) -> t.Iterator[ExprIR]: yield from self.truthy.iter_output_name() - - def map_ir(self, function: MapIR, /) -> ExprIR: - predicate = self.predicate.map_ir(function) - truthy = self.truthy.map_ir(function) - falsy = self.falsy.map_ir(function) - changed = common.replace(self, predicate=predicate, truthy=truthy, falsy=falsy) - return function(changed) diff --git a/narwhals/_plan/name.py b/narwhals/_plan/name.py index 7396500459..4147f20450 100644 --- a/narwhals/_plan/name.py +++ b/narwhals/_plan/name.py @@ -4,18 +4,16 @@ from narwhals._plan import common from narwhals._plan._immutable import Immutable -from narwhals._plan.common import ExprIR from narwhals._plan.options import ExprIROptions if TYPE_CHECKING: from narwhals._compliant.typing import AliasName from narwhals._plan.dummy import Expr - from narwhals._plan.typing import MapIR -class KeepName(ExprIR, child=("expr",), config=ExprIROptions.no_dispatch()): +class KeepName(common.ExprIR, child=("expr",), config=ExprIROptions.no_dispatch()): __slots__ = ("expr",) - expr: ExprIR + expr: common.ExprIR @property def is_scalar(self) -> bool: @@ -24,13 +22,10 @@ def is_scalar(self) -> bool: def __repr__(self) -> str: return f"{self.expr!r}.name.keep()" - def map_ir(self, function: MapIR, /) -> ExprIR: - return function(common.replace(self, expr=self.expr.map_ir(function))) - -class RenameAlias(ExprIR, child=("expr",), config=ExprIROptions.no_dispatch()): +class RenameAlias(common.ExprIR, child=("expr",), config=ExprIROptions.no_dispatch()): __slots__ = ("expr", "function") - expr: ExprIR + expr: common.ExprIR function: AliasName @property @@ -40,9 +35,6 @@ def is_scalar(self) -> bool: def __repr__(self) -> str: return f".rename_alias({self.expr!r})" - def map_ir(self, function: MapIR, /) -> ExprIR: - return function(common.replace(self, expr=self.expr.map_ir(function))) - class Prefix(Immutable): __slots__ = ("prefix",) From c7cd9cc3729491647e7388e1d9b3e99c66d0c65a Mon Sep 17 00:00:00 2001 From: dangotbanned <125183946+dangotbanned@users.noreply.github.com> Date: Wed, 3 Sep 2025 14:31:15 +0000 Subject: [PATCH 5/5] refactor: Remove unused `FunctionExpr.with_options` --- narwhals/_plan/expr.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/narwhals/_plan/expr.py b/narwhals/_plan/expr.py index c7067a64e8..7d35237f16 100644 --- a/narwhals/_plan/expr.py +++ b/narwhals/_plan/expr.py @@ -6,7 +6,6 @@ # - Literal import typing as t -from narwhals._plan import common from narwhals._plan.aggregation import AggExpr, OrderableAggExpr from narwhals._plan.common import ExprIR, SelectorIR, collect from narwhals._plan.exceptions import function_expr_invalid_operation_error @@ -278,9 +277,6 @@ class FunctionExpr(ExprIR, t.Generic[FunctionT], child=("input",)): def is_scalar(self) -> bool: return self.function.is_scalar - def with_options(self, options: FunctionOptions, /) -> Self: - return common.replace(self, options=self.options.with_flags(options.flags)) - def __repr__(self) -> str: if self.input: first = self.input[0]