Repository: ibis-project/ibis

Issue:
bug: TypeError: 'DeferredAttr' object is not iterable when calling `t.drop(_.a)`
### What happened?

When testing the fix for #6872 I discovered that it does not cover the use of ibis' `_`:

```python
>>> import ibis
... from ibis import _
... 
... t = ibis.memtable({"a": [1], "b": [0.1]})
... t.drop("a")
r0 := InMemoryTable
  data:
    PandasDataFrameProxy:
         a    b
      0  1  0.1

Selection[r0]
  selections:
    b: r0.b
>>> t.drop(t.a)
r0 := InMemoryTable
  data:
    PandasDataFrameProxy:
         a    b
      0  1  0.1

Selection[r0]
  selections:
    b: r0.b
```

But then:

```python
>>> t.drop(_.a)
Traceback (most recent call last):
  Cell In[16], line 1
    t.drop(_.a)
  File ~/code/ibis/ibis/expr/types/relations.py:1933 in drop
    return self.select(~s._to_selector(fields))
  File ~/code/ibis/ibis/selectors.py:678 in _to_selector
    return any_of(*obj)
  File ~/code/ibis/ibis/selectors.py:358 in any_of
    return functools.reduce(operator.or_, map(_to_selector, predicates))
  File ~/code/ibis/ibis/selectors.py:678 in _to_selector
    return any_of(*obj)
  File ~/code/ibis/ibis/expr/deferred.py:63 in __iter__
    raise TypeError(f"{self.__class__.__name__!r} object is not iterable")
TypeError: 'DeferredAttr' object is not iterable
```

### What version of ibis are you using?

Today's master branch.

### What backend(s) are you using, if any?

DuckDB but it should not depend on the choice of backend.

### Relevant log output

_No response_

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion:
BTW, I noticed that #6879 only added unit tests for selectors, but no new non-regression / integration tests related to the use of the `drop` method itself.

I understand that testing every valid argument option in "integration" tests would be too cumbersome to maintain and expensive/slow to run but maybe there is a middle ground?
Thanks for the followup report, @ogrisel !  Yeah, #6879 was perhaps a bit too slapdash -- I'll add some more robust tests along with a fix for this.
No problem. TIL a new English word: "slapdash".

Resolving patch:
diff --git a/ibis/expr/types/relations.py b/ibis/expr/types/relations.py
--- a/ibis/expr/types/relations.py
+++ b/ibis/expr/types/relations.py
@@ -1925,6 +1925,11 @@ def drop(self, *fields: str | Selector) -> Table:
             # no-op if nothing to be dropped
             return self
 
+        fields = tuple(
+            field.resolve(self) if isinstance(field, Deferred) else field
+            for field in fields
+        )
+
         if missing_fields := {f for f in fields if isinstance(f, str)}.difference(
             self.schema().names
         ):


Test patch:
diff --git a/ibis/tests/expr/test_table.py b/ibis/tests/expr/test_table.py
--- a/ibis/tests/expr/test_table.py
+++ b/ibis/tests/expr/test_table.py
@@ -1626,6 +1626,15 @@ def test_drop():
 
     assert res.equals(t.drop(s.matches("a|b")))
 
+    res = t.drop(_.a)
+    assert res.equals(t.select("b", "c", "d"))
+
+    res = t.drop(_.a, _.b)
+    assert res.equals(t.select("c", "d"))
+
+    res = t.drop(_.a, "b")
+    assert res.equals(t.select("c", "d"))
+
     with pytest.raises(KeyError):
         t.drop("e")