Repository: narwhals-dev/narwhals (python)

Issue:
test: check if we have a test for `nw.when(nw.col('a')>1).then(nw.lit(0)).otherwise(nw.lit(1))`
Do we have a case where both are literals?

It would be good to investigate this, and add a test if necessary

Pull request:
patch: fix when-then double lit case
## What type of PR is this? (check all applicable)

- [ ] 💾 Refactor
- [ ] ✨ Feature
- [x] 🐛 Bug Fix
- [ ] 🔧 Optimization
- [ ] 📝 Documentation
- [ ] ✅ Test
- [ ] 🐳 Other

## Related issues

- Related issue #\<issue number\>
- Closes #1722 

## Checklist

- [x] Code follows style guide (ruff)
- [x] Tests added
- [ ] Documented the changes

## If you have comments or can explain your changes, please do so below

Definitly not too proud of this implementation

Patch:
diff --git a/narwhals/_arrow/dataframe.py b/narwhals/_arrow/dataframe.py
index ed738647..96a8ef71 100644
--- a/narwhals/_arrow/dataframe.py
+++ b/narwhals/_arrow/dataframe.py
@@ -395,10 +395,9 @@ class ArrowDataFrame(CompliantDataFrame, CompliantLazyFrame):
         )
         return self._from_native_frame(self._native_frame.drop(to_drop))
 
-    def drop_nulls(self: Self, subset: str | list[str] | None) -> Self:
+    def drop_nulls(self: Self, subset: list[str] | None) -> Self:
         if subset is None:
             return self._from_native_frame(self._native_frame.drop_null())
-        subset = [subset] if isinstance(subset, str) else subset
         plx = self.__narwhals_namespace__()
         return self.filter(~plx.any_horizontal(plx.col(*subset).is_null()))
 
diff --git a/narwhals/_arrow/namespace.py b/narwhals/_arrow/namespace.py
index b02ad32e..70d372a5 100644
--- a/narwhals/_arrow/namespace.py
+++ b/narwhals/_arrow/namespace.py
@@ -435,19 +435,18 @@ class ArrowWhen:
 
         plx = df.__narwhals_namespace__()
         condition = parse_into_expr(self._condition, namespace=plx)(df)[0]
+
         try:
             value_series = parse_into_expr(self._then_value, namespace=plx)(df)[0]
         except TypeError:
-            # `self._otherwise_value` is a scalar and can't be converted to an expression
-            value_series = condition.__class__._from_iterable(
-                pa.repeat(pa.scalar(self._then_value), len(condition)),
-                name="literal",
-                backend_version=self._backend_version,
-                version=self._version,
+            # `self._then_value` is a scalar and can't be converted to an expression
+            value_series = plx._create_series_from_scalar(
+                self._then_value, reference_series=condition
             )
 
-        value_series_native = value_series._native_series
-        condition_native = condition._native_series
+        condition_native, value_series_native = broadcast_series(
+            [condition, value_series]
+        )
 
         if self._otherwise_value is None:
             otherwise_native = pa.repeat(
@@ -472,9 +471,7 @@ class ArrowWhen:
             ]
         else:
             otherwise_series = otherwise_expr(df)[0]
-            condition_native, otherwise_native = broadcast_series(
-                [condition, otherwise_series]
-            )
+            _, otherwise_native = broadcast_series([condition, otherwise_series])
             return [
                 value_series._from_native_series(
                     pc.if_else(condition_native, value_series_native, otherwise_native)
diff --git a/narwhals/_dask/dataframe.py b/narwhals/_dask/dataframe.py
index 89e1bc48..1a8efc44 100644
--- a/narwhals/_dask/dataframe.py
+++ b/narwhals/_dask/dataframe.py
@@ -149,10 +149,9 @@ class DaskLazyFrame(CompliantLazyFrame):
         )
         return self._from_native_frame(df)
 
-    def drop_nulls(self: Self, subset: str | list[str] | None) -> Self:
+    def drop_nulls(self: Self, subset: list[str] | None) -> Self:
         if subset is None:
             return self._from_native_frame(self._native_frame.dropna())
-        subset = [subset] if isinstance(subset, str) else subset
         plx = self.__narwhals_namespace__()
         return self.filter(~plx.any_horizontal(plx.col(*subset).is_null()))
 
diff --git a/narwhals/_dask/namespace.py b/narwhals/_dask/namespace.py
index 23805afd..936b9abb 100644
--- a/narwhals/_dask/namespace.py
+++ b/narwhals/_dask/namespace.py
@@ -398,13 +398,24 @@ class DaskWhen:
         plx = df.__narwhals_namespace__()
         condition = parse_into_expr(self._condition, namespace=plx)(df)[0]
         condition = cast("dx.Series", condition)
+
         try:
-            value_series = parse_into_expr(self._then_value, namespace=plx)(df)[0]
+            then_expr = parse_into_expr(self._then_value, namespace=plx)
         except TypeError:
-            # `self._otherwise_value` is a scalar and can't be converted to an expression
+            # `self._then_value` is a scalar and can't be converted to an expression
+            value_sequence: Sequence[Any] = [self._then_value]
+            is_scalar = True
+        else:
+            is_scalar = then_expr._returns_scalar  # type: ignore[attr-defined]
+            value_sequence = then_expr(df)[0]
+
+        if is_scalar:
             _df = condition.to_frame("a")
-            _df["tmp"] = self._then_value
+            _df["tmp"] = value_sequence[0]
             value_series = _df["tmp"]
+        else:
+            value_series = value_sequence
+
         value_series = cast("dx.Series", value_series)
         validate_comparand(condition, value_series)
 
diff --git a/narwhals/_duckdb/dataframe.py b/narwhals/_duckdb/dataframe.py
index 98eca0bd..a3c2798b 100644
--- a/narwhals/_duckdb/dataframe.py
+++ b/narwhals/_duckdb/dataframe.py
@@ -321,3 +321,12 @@ class DuckDBLazyFrame:
             )
         )
         return self._from_native_frame(result)
+
+    def drop_nulls(self: Self, subset: list[str] | None) -> Self:
+        import duckdb
+
+        rel = self._native_frame
+        subset_ = subset if subset is not None else rel.columns
+        keep_condition = " and ".join(f'"{col}" is not null' for col in subset_)
+        query = f"select * from rel where {keep_condition}"  # noqa: S608
+        return self._from_native_frame(duckdb.sql(query))
diff --git a/narwhals/_pandas_like/dataframe.py b/narwhals/_pandas_like/dataframe.py
index 47c43a69..fdd53b4a 100644
--- a/narwhals/_pandas_like/dataframe.py
+++ b/narwhals/_pandas_like/dataframe.py
@@ -371,10 +371,9 @@ class PandasLikeDataFrame:
         )
         return self._from_native_frame(df)
 
-    def drop_nulls(self, subset: str | list[str] | None) -> Self:
+    def drop_nulls(self, subset: list[str] | None) -> Self:
         if subset is None:
             return self._from_native_frame(self._native_frame.dropna(axis=0))
-        subset = [subset] if isinstance(subset, str) else subset
         plx = self.__narwhals_namespace__()
         return self.filter(~plx.any_horizontal(plx.col(*subset).is_null()))
 
diff --git a/narwhals/_pandas_like/namespace.py b/narwhals/_pandas_like/namespace.py
index 52e56d34..4265a340 100644
--- a/narwhals/_pandas_like/namespace.py
+++ b/narwhals/_pandas_like/namespace.py
@@ -461,20 +461,17 @@ class PandasWhen:
 
         plx = df.__narwhals_namespace__()
         condition = parse_into_expr(self._condition, namespace=plx)(df)[0]
+
         try:
             value_series = parse_into_expr(self._then_value, namespace=plx)(df)[0]
         except TypeError:
-            # `self._otherwise_value` is a scalar and can't be converted to an expression
-            value_series = condition.__class__._from_iterable(
-                [self._then_value] * len(condition),
-                name="literal",
-                index=condition._native_series.index,
-                implementation=self._implementation,
-                backend_version=self._backend_version,
-                version=self._version,
+            # `self._then_value` is a scalar and can't be converted to an expression
+            value_series = plx._create_series_from_scalar(
+                self._then_value, reference_series=condition
             )
-        value_series_native, condition_native = broadcast_align_and_extract_native(
-            value_series, condition
+
+        condition_native, value_series_native = broadcast_align_and_extract_native(
+            condition, value_series
         )
 
         if self._otherwise_value is None:
@@ -494,7 +491,14 @@ class PandasWhen:
             ]
         else:
             otherwise_series = otherwise_expr(df)[0]
-            return [value_series.zip_with(condition, otherwise_series)]
+            _, otherwise_native = broadcast_align_and_extract_native(
+                condition, otherwise_series
+            )
+            return [
+                value_series._from_native_series(
+                    value_series_native.where(condition_native, otherwise_native)
+                )
+            ]
 
     def then(self, value: PandasLikeExpr | PandasLikeSeries | Any) -> PandasThen:
         self._then_value = value
diff --git a/narwhals/_spark_like/dataframe.py b/narwhals/_spark_like/dataframe.py
index 7e7f41dd..34da43fd 100644
--- a/narwhals/_spark_like/dataframe.py
+++ b/narwhals/_spark_like/dataframe.py
@@ -183,7 +183,7 @@ class SparkLikeLazyFrame:
         sort_cols = [sort_f(col) for col, sort_f in zip(flat_by, sort_funcs)]
         return self._from_native_frame(self._native_frame.sort(*sort_cols))
 
-    def drop_nulls(self: Self, subset: str | list[str] | None) -> Self:
+    def drop_nulls(self: Self, subset: list[str] | None) -> Self:
         return self._from_native_frame(self._native_frame.dropna(subset=subset))
 
     def rename(self: Self, mapping: dict[str, str]) -> Self:
diff --git a/narwhals/dataframe.py b/narwhals/dataframe.py
index 1ae43028..e4504167 100644
--- a/narwhals/dataframe.py
+++ b/narwhals/dataframe.py
@@ -107,7 +107,8 @@ class BaseFrame(Generic[FrameT]):
             self._compliant_frame.with_row_index(name),
         )
 
-    def drop_nulls(self: Self, subset: str | list[str] | None = None) -> Self:
+    def drop_nulls(self: Self, subset: str | list[str] | None) -> Self:
+        subset = [subset] if isinstance(subset, str) else subset
         return self._from_compliant_dataframe(
             self._compliant_frame.drop_nulls(subset=subset),
         )


Test patch:
diff --git a/tests/expr_and_series/when_test.py b/tests/expr_and_series/when_test.py
index 94e37aaa..4f768db0 100644
--- a/tests/expr_and_series/when_test.py
+++ b/tests/expr_and_series/when_test.py
@@ -141,3 +141,13 @@ def test_when_then_otherwise_lit_str(
     result = df.select(nw.when(nw.col("a") > 1).then(nw.col("b")).otherwise(nw.lit("z")))
     expected = {"b": ["z", "b", "c"]}
     assert_equal_data(result, expected)
+
+
+def test_when_then_otherwise_both_lit(constructor: Constructor) -> None:
+    df = nw.from_native(constructor(data))
+    result = df.select(
+        x1=nw.when(nw.col("a") > 1).then(nw.lit(42)).otherwise(nw.lit(-1)),
+        x2=nw.when(nw.col("a") > 2).then(nw.lit(42)).otherwise(nw.lit(-1)),
+    )
+    expected = {"x1": [-1, 42, 42], "x2": [-1, -1, 42]}
+    assert_equal_data(result, expected)
diff --git a/tests/frame/drop_nulls_test.py b/tests/frame/drop_nulls_test.py
index 368ad6ba..c49b1712 100644
--- a/tests/frame/drop_nulls_test.py
+++ b/tests/frame/drop_nulls_test.py
@@ -7,18 +7,16 @@ from tests.utils import Constructor
 from tests.utils import assert_equal_data
 
 data = {
-    "a": [1.0, 2.0, None, 4.0],
-    "b": [None, 3.0, None, 5.0],
+    "alpha": [1.0, 2.0, None, 4.0],
+    "beta gamma": [None, 3.0, None, 5.0],
 }
 
 
-def test_drop_nulls(constructor: Constructor, request: pytest.FixtureRequest) -> None:
-    if "duckdb" in str(constructor):
-        request.applymarker(pytest.mark.xfail)
+def test_drop_nulls(constructor: Constructor) -> None:
     result = nw.from_native(constructor(data)).drop_nulls()
     expected = {
-        "a": [2.0, 4.0],
-        "b": [3.0, 5.0],
+        "alpha": [2.0, 4.0],
+        "beta gamma": [3.0, 5.0],
     }
     assert_equal_data(result, expected)
 
@@ -26,18 +24,15 @@ def test_drop_nulls(constructor: Constructor, request: pytest.FixtureRequest) ->
 @pytest.mark.parametrize(
     ("subset", "expected"),
     [
-        ("a", {"a": [1, 2.0, 4.0], "b": [None, 3.0, 5.0]}),
-        (["a"], {"a": [1, 2.0, 4.0], "b": [None, 3.0, 5.0]}),
-        (["a", "b"], {"a": [2.0, 4.0], "b": [3.0, 5.0]}),
+        ("alpha", {"alpha": [1, 2.0, 4.0], "beta gamma": [None, 3.0, 5.0]}),
+        (["alpha"], {"alpha": [1, 2.0, 4.0], "beta gamma": [None, 3.0, 5.0]}),
+        (["alpha", "beta gamma"], {"alpha": [2.0, 4.0], "beta gamma": [3.0, 5.0]}),
     ],
 )
 def test_drop_nulls_subset(
     constructor: Constructor,
     subset: str | list[str],
     expected: dict[str, float],
-    request: pytest.FixtureRequest,
 ) -> None:
-    if "duckdb" in str(constructor):
-        request.applymarker(pytest.mark.xfail)
     result = nw.from_native(constructor(data)).drop_nulls(subset=subset)
     assert_equal_data(result, expected)