Repository: ibis-project/ibis

Issue:
bug: `t.drop(t.a)` unexpectedly selects `BitwiseNot(a)` instead of selecting the other columns
### What happened?

Reproducer setup:

```python
import ibis

t = ibis.memtable({"a": [1], "b": [0.1]})
t.drop("a")
```

works as expected (selects column "b" and discards column "a"):

```
r0 := InMemoryTable
  data:
    PandasDataFrameProxy:
         a    b
      0  1  0.1

Selection[r0]
  selections:
    b: r0.b
```

however, the following does not do the same, and attempts to perform an `BitwiseNot` operation instead:

```python
t.drop(t.a)
```

```
r0 := InMemoryTable
  data:
    PandasDataFrameProxy:
         a    b
      0  1  0.1

Selection[r0]
  selections:
    BitwiseNot(a): BitwiseNot(r0.a)
```

For column types for which the negation operator is not valid, this causes the following crash:

```python
t.drop(t.b)
```
```python-traceback
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[7], line 1
----> 1 t.drop(t.b)

File ~/code/ibis/ibis/expr/types/relations.py:1940, in Table.drop(self, *fields)
   1937     raise KeyError(f"Fields not in table: {sorted(missing_fields)}")
   1939 sels = (s.c(f) if isinstance(f, str) else f for f in fields)
-> 1940 return self.select(~s.any_of(*sels))

TypeError: bad operand type for unary ~: 'FloatingColumn'
```

### What version of ibis are you using?

6.1.0 (actually the dev source code from today).

### What backend(s) are you using, if any?

DuckDB but it seems independent of the backend.

### Relevant log output

_No response_

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion:
Thanks @ogrisel!

Yeah, we should definitely allow column references in drop.

Resolving patch:
diff --git a/ibis/selectors.py b/ibis/selectors.py
--- a/ibis/selectors.py
+++ b/ibis/selectors.py
@@ -365,9 +365,9 @@ def all_of(*predicates: str | Predicate) -> Predicate:
 
 
 @public
-def c(*names: str) -> Predicate:
+def c(*names: str | ir.Column) -> Predicate:
     """Select specific column names."""
-    names = frozenset(names)
+    names = frozenset(col if isinstance(col, str) else col.get_name() for col in names)
 
     def func(col: ir.Value) -> bool:
         schema = col.op().table.schema
@@ -664,10 +664,14 @@ def all() -> Predicate:
     return r[:]
 
 
-def _to_selector(obj: str | Selector | Sequence[str | Selector]) -> Selector:
+def _to_selector(
+    obj: str | Selector | ir.Column | Sequence[str | Selector | ir.Column],
+) -> Selector:
     """Convert an object to a `Selector`."""
     if isinstance(obj, Selector):
         return obj
+    elif isinstance(obj, ir.Column):
+        return c(obj.get_name())
     elif isinstance(obj, str):
         return c(obj)
     else:


Test patch:
diff --git a/ibis/tests/expr/test_selectors.py b/ibis/tests/expr/test_selectors.py
--- a/ibis/tests/expr/test_selectors.py
+++ b/ibis/tests/expr/test_selectors.py
@@ -157,8 +157,12 @@ def zscore(c):
             s.across(s.numeric() & ~s.c("year"), (_ - _.mean()) / _.std())
         ),
         lambda t: t.select(s.across(s.numeric() & ~s.c("year"), zscore)),
+        lambda t: t.select(
+            s.across(s.numeric() & ~s.c(t.year), (_ - _.mean()) / _.std())
+        ),
+        lambda t: t.select(s.across(s.numeric() & ~s.c(t.year), zscore)),
     ],
-    ids=["deferred", "func"],
+    ids=["deferred", "func", "deferred-column-ref", "func-column-ref"],
 )
 def test_across_select(penguins, expr_func):
     expr = expr_func(penguins)