You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by bl...@apache.org on 2022/11/19 22:21:35 UTC

[iceberg] branch master updated: Python: Remove dataclasses (#6139)

This is an automated email from the ASF dual-hosted git repository.

blue pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iceberg.git


The following commit(s) were added to refs/heads/master by this push:
     new dbf91118c9 Python: Remove dataclasses (#6139)
dbf91118c9 is described below

commit dbf91118c9333977fd0a3dcfc294c646dbd5e2b9
Author: Fokko Driesprong <fo...@apache.org>
AuthorDate: Sat Nov 19 23:21:28 2022 +0100

    Python: Remove dataclasses (#6139)
---
 python/poetry.lock                           | 177 +++++----
 python/pyiceberg/expressions/__init__.py     | 526 ++++++++++++++++----------
 python/pyiceberg/expressions/literals.py     |  99 ++---
 python/pyiceberg/expressions/visitors.py     | 126 +++----
 python/pyiceberg/typedef.py                  |   7 +-
 python/pyproject.toml                        |   1 +
 python/tests/expressions/test_expressions.py | 522 +++++++++++++++++---------
 python/tests/expressions/test_literals.py    |  41 +-
 python/tests/expressions/test_visitors.py    | 535 ++++++++++++---------------
 python/tests/table/test_init.py              |  12 +-
 10 files changed, 1144 insertions(+), 902 deletions(-)

diff --git a/python/poetry.lock b/python/poetry.lock
index 2cb2689f57..e5157bda8c 100644
--- a/python/poetry.lock
+++ b/python/poetry.lock
@@ -49,11 +49,11 @@ typing_extensions = {version = ">=4.0", markers = "python_version < \"3.10\""}
 
 [[package]]
 name = "aiosignal"
-version = "1.2.0"
+version = "1.3.1"
 description = "aiosignal: a list of registered asynchronous callbacks"
 category = "main"
 optional = true
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 
 [package.dependencies]
 frozenlist = ">=1.1.0"
@@ -216,7 +216,7 @@ python-versions = ">=3.7"
 
 [[package]]
 name = "exceptiongroup"
-version = "1.0.0"
+version = "1.0.4"
 description = "Backport of PEP 654 (exception groups)"
 category = "dev"
 optional = false
@@ -253,7 +253,7 @@ testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pyt
 
 [[package]]
 name = "frozenlist"
-version = "1.3.1"
+version = "1.3.3"
 description = "A list-like structure which implements collections.abc.MutableSequence"
 category = "main"
 optional = true
@@ -400,15 +400,15 @@ tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
 
 [[package]]
 name = "platformdirs"
-version = "2.5.2"
-description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+version = "2.5.4"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
 category = "dev"
 optional = false
 python-versions = ">=3.7"
 
 [package.extras]
-docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"]
-test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
+docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"]
+test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
 
 [[package]]
 name = "pluggy"
@@ -627,7 +627,7 @@ boto3 = ["aiobotocore[boto3] (>=2.4.0,<2.5.0)"]
 
 [[package]]
 name = "setuptools"
-version = "65.5.0"
+version = "65.5.1"
 description = "Easily download, build, install, upgrade, and uninstall Python packages"
 category = "dev"
 optional = false
@@ -635,7 +635,7 @@ python-versions = ">=3.7"
 
 [package.extras]
 docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
-testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
+testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
 testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
 
 [[package]]
@@ -701,7 +701,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
 
 [[package]]
 name = "virtualenv"
-version = "20.16.6"
+version = "20.16.7"
 description = "Virtual Python Environment builder"
 category = "dev"
 optional = false
@@ -771,7 +771,7 @@ snappy = ["python-snappy"]
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.8"
-content-hash = "4f90d01e509c4918b7e4ce6da629f5e009165a499cd2f43c25f3ef73ef58af86"
+content-hash = "fda6f8ad5abce655e854325c9e0e944c6ad818487479c97078d1dce93496465e"
 
 [metadata.files]
 aiobotocore = [
@@ -872,8 +872,8 @@ aioitertools = [
     {file = "aioitertools-0.11.0.tar.gz", hash = "sha256:42c68b8dd3a69c2bf7f2233bf7df4bb58b557bca5252ac02ed5187bbc67d6831"},
 ]
 aiosignal = [
-    {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"},
-    {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"},
+    {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"},
+    {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"},
 ]
 async-timeout = [
     {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
@@ -1042,8 +1042,8 @@ docutils = [
     {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"},
 ]
 exceptiongroup = [
-    {file = "exceptiongroup-1.0.0-py3-none-any.whl", hash = "sha256:2ac84b496be68464a2da60da518af3785fff8b7ec0d090a581604bc870bdee41"},
-    {file = "exceptiongroup-1.0.0.tar.gz", hash = "sha256:affbabf13fb6e98988c38d9c5650e701569fe3c1de3233cfb61c5f33774690ad"},
+    {file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"},
+    {file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"},
 ]
 fastavro = [
     {file = "fastavro-1.6.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:de244146a3d7bc4e60c7be11f317054ee0e57ca807d85923bd8407981488ff18"},
@@ -1069,65 +1069,80 @@ filelock = [
     {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"},
 ]
 frozenlist = [
-    {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5f271c93f001748fc26ddea409241312a75e13466b06c94798d1a341cf0e6989"},
-    {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9c6ef8014b842f01f5d2b55315f1af5cbfde284eb184075c189fd657c2fd8204"},
-    {file = "frozenlist-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:219a9676e2eae91cb5cc695a78b4cb43d8123e4160441d2b6ce8d2c70c60e2f3"},
-    {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b47d64cdd973aede3dd71a9364742c542587db214e63b7529fbb487ed67cddd9"},
-    {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2af6f7a4e93f5d08ee3f9152bce41a6015b5cf87546cb63872cc19b45476e98a"},
-    {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a718b427ff781c4f4e975525edb092ee2cdef6a9e7bc49e15063b088961806f8"},
-    {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c56c299602c70bc1bb5d1e75f7d8c007ca40c9d7aebaf6e4ba52925d88ef826d"},
-    {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:717470bfafbb9d9be624da7780c4296aa7935294bd43a075139c3d55659038ca"},
-    {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:31b44f1feb3630146cffe56344704b730c33e042ffc78d21f2125a6a91168131"},
-    {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c3b31180b82c519b8926e629bf9f19952c743e089c41380ddca5db556817b221"},
-    {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d82bed73544e91fb081ab93e3725e45dd8515c675c0e9926b4e1f420a93a6ab9"},
-    {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49459f193324fbd6413e8e03bd65789e5198a9fa3095e03f3620dee2f2dabff2"},
-    {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:94e680aeedc7fd3b892b6fa8395b7b7cc4b344046c065ed4e7a1e390084e8cb5"},
-    {file = "frozenlist-1.3.1-cp310-cp310-win32.whl", hash = "sha256:fabb953ab913dadc1ff9dcc3a7a7d3dc6a92efab3a0373989b8063347f8705be"},
-    {file = "frozenlist-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:eee0c5ecb58296580fc495ac99b003f64f82a74f9576a244d04978a7e97166db"},
-    {file = "frozenlist-1.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0bc75692fb3770cf2b5856a6c2c9de967ca744863c5e89595df64e252e4b3944"},
-    {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086ca1ac0a40e722d6833d4ce74f5bf1aba2c77cbfdc0cd83722ffea6da52a04"},
-    {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b51eb355e7f813bcda00276b0114c4172872dc5fb30e3fea059b9367c18fbcb"},
-    {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74140933d45271c1a1283f708c35187f94e1256079b3c43f0c2267f9db5845ff"},
-    {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee4c5120ddf7d4dd1eaf079af3af7102b56d919fa13ad55600a4e0ebe532779b"},
-    {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97d9e00f3ac7c18e685320601f91468ec06c58acc185d18bb8e511f196c8d4b2"},
-    {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e19add867cebfb249b4e7beac382d33215d6d54476bb6be46b01f8cafb4878b"},
-    {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a027f8f723d07c3f21963caa7d585dcc9b089335565dabe9c814b5f70c52705a"},
-    {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:61d7857950a3139bce035ad0b0945f839532987dfb4c06cfe160254f4d19df03"},
-    {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:53b2b45052e7149ee8b96067793db8ecc1ae1111f2f96fe1f88ea5ad5fd92d10"},
-    {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bbb1a71b1784e68870800b1bc9f3313918edc63dbb8f29fbd2e767ce5821696c"},
-    {file = "frozenlist-1.3.1-cp37-cp37m-win32.whl", hash = "sha256:ab6fa8c7871877810e1b4e9392c187a60611fbf0226a9e0b11b7b92f5ac72792"},
-    {file = "frozenlist-1.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f89139662cc4e65a4813f4babb9ca9544e42bddb823d2ec434e18dad582543bc"},
-    {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4c0c99e31491a1d92cde8648f2e7ccad0e9abb181f6ac3ddb9fc48b63301808e"},
-    {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:61e8cb51fba9f1f33887e22488bad1e28dd8325b72425f04517a4d285a04c519"},
-    {file = "frozenlist-1.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc2f3e368ee5242a2cbe28323a866656006382872c40869b49b265add546703f"},
-    {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58fb94a01414cddcdc6839807db77ae8057d02ddafc94a42faee6004e46c9ba8"},
-    {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:022178b277cb9277d7d3b3f2762d294f15e85cd2534047e68a118c2bb0058f3e"},
-    {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:572ce381e9fe027ad5e055f143763637dcbac2542cfe27f1d688846baeef5170"},
-    {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19127f8dcbc157ccb14c30e6f00392f372ddb64a6ffa7106b26ff2196477ee9f"},
-    {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42719a8bd3792744c9b523674b752091a7962d0d2d117f0b417a3eba97d1164b"},
-    {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2743bb63095ef306041c8f8ea22bd6e4d91adabf41887b1ad7886c4c1eb43d5f"},
-    {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:fa47319a10e0a076709644a0efbcaab9e91902c8bd8ef74c6adb19d320f69b83"},
-    {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52137f0aea43e1993264a5180c467a08a3e372ca9d378244c2d86133f948b26b"},
-    {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:f5abc8b4d0c5b556ed8cd41490b606fe99293175a82b98e652c3f2711b452988"},
-    {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1e1cf7bc8cbbe6ce3881863671bac258b7d6bfc3706c600008925fb799a256e2"},
-    {file = "frozenlist-1.3.1-cp38-cp38-win32.whl", hash = "sha256:0dde791b9b97f189874d654c55c24bf7b6782343e14909c84beebd28b7217845"},
-    {file = "frozenlist-1.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:9494122bf39da6422b0972c4579e248867b6b1b50c9b05df7e04a3f30b9a413d"},
-    {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31bf9539284f39ff9398deabf5561c2b0da5bb475590b4e13dd8b268d7a3c5c1"},
-    {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e0c8c803f2f8db7217898d11657cb6042b9b0553a997c4a0601f48a691480fab"},
-    {file = "frozenlist-1.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da5ba7b59d954f1f214d352308d1d86994d713b13edd4b24a556bcc43d2ddbc3"},
-    {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e6b2b456f21fc93ce1aff2b9728049f1464428ee2c9752a4b4f61e98c4db96"},
-    {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526d5f20e954d103b1d47232e3839f3453c02077b74203e43407b962ab131e7b"},
-    {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b499c6abe62a7a8d023e2c4b2834fce78a6115856ae95522f2f974139814538c"},
-    {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab386503f53bbbc64d1ad4b6865bf001414930841a870fc97f1546d4d133f141"},
-    {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f63c308f82a7954bf8263a6e6de0adc67c48a8b484fab18ff87f349af356efd"},
-    {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:12607804084d2244a7bd4685c9d0dca5df17a6a926d4f1967aa7978b1028f89f"},
-    {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:da1cdfa96425cbe51f8afa43e392366ed0b36ce398f08b60de6b97e3ed4affef"},
-    {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f810e764617b0748b49a731ffaa525d9bb36ff38332411704c2400125af859a6"},
-    {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:35c3d79b81908579beb1fb4e7fcd802b7b4921f1b66055af2578ff7734711cfa"},
-    {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c92deb5d9acce226a501b77307b3b60b264ca21862bd7d3e0c1f3594022f01bc"},
-    {file = "frozenlist-1.3.1-cp39-cp39-win32.whl", hash = "sha256:5e77a8bd41e54b05e4fb2708dc6ce28ee70325f8c6f50f3df86a44ecb1d7a19b"},
-    {file = "frozenlist-1.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:625d8472c67f2d96f9a4302a947f92a7adbc1e20bedb6aff8dbc8ff039ca6189"},
-    {file = "frozenlist-1.3.1.tar.gz", hash = "sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8"},
+    {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4"},
+    {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0"},
+    {file = "frozenlist-1.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b1c63e8d377d039ac769cd0926558bb7068a1f7abb0f003e3717ee003ad85530"},
+    {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fdfc24dcfce5b48109867c13b4cb15e4660e7bd7661741a391f821f23dfdca7"},
+    {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c926450857408e42f0bbc295e84395722ce74bae69a3b2aa2a65fe22cb14b99"},
+    {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1841e200fdafc3d51f974d9d377c079a0694a8f06de2e67b48150328d66d5483"},
+    {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f470c92737afa7d4c3aacc001e335062d582053d4dbe73cda126f2d7031068dd"},
+    {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:783263a4eaad7c49983fe4b2e7b53fa9770c136c270d2d4bbb6d2192bf4d9caf"},
+    {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:924620eef691990dfb56dc4709f280f40baee568c794b5c1885800c3ecc69816"},
+    {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae4dc05c465a08a866b7a1baf360747078b362e6a6dbeb0c57f234db0ef88ae0"},
+    {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bed331fe18f58d844d39ceb398b77d6ac0b010d571cba8267c2e7165806b00ce"},
+    {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:02c9ac843e3390826a265e331105efeab489ffaf4dd86384595ee8ce6d35ae7f"},
+    {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9545a33965d0d377b0bc823dcabf26980e77f1b6a7caa368a365a9497fb09420"},
+    {file = "frozenlist-1.3.3-cp310-cp310-win32.whl", hash = "sha256:d5cd3ab21acbdb414bb6c31958d7b06b85eeb40f66463c264a9b343a4e238642"},
+    {file = "frozenlist-1.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:b756072364347cb6aa5b60f9bc18e94b2f79632de3b0190253ad770c5df17db1"},
+    {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4395e2f8d83fbe0c627b2b696acce67868793d7d9750e90e39592b3626691b7"},
+    {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14143ae966a6229350021384870458e4777d1eae4c28d1a7aa47f24d030e6678"},
+    {file = "frozenlist-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d8860749e813a6f65bad8285a0520607c9500caa23fea6ee407e63debcdbef6"},
+    {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23d16d9f477bb55b6154654e0e74557040575d9d19fe78a161bd33d7d76808e8"},
+    {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb82dbba47a8318e75f679690190c10a5e1f447fbf9df41cbc4c3afd726d88cb"},
+    {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9309869032abb23d196cb4e4db574232abe8b8be1339026f489eeb34a4acfd91"},
+    {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a97b4fe50b5890d36300820abd305694cb865ddb7885049587a5678215782a6b"},
+    {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c188512b43542b1e91cadc3c6c915a82a5eb95929134faf7fd109f14f9892ce4"},
+    {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:303e04d422e9b911a09ad499b0368dc551e8c3cd15293c99160c7f1f07b59a48"},
+    {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0771aed7f596c7d73444c847a1c16288937ef988dc04fb9f7be4b2aa91db609d"},
+    {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:66080ec69883597e4d026f2f71a231a1ee9887835902dbe6b6467d5a89216cf6"},
+    {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:41fe21dc74ad3a779c3d73a2786bdf622ea81234bdd4faf90b8b03cad0c2c0b4"},
+    {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f20380df709d91525e4bee04746ba612a4df0972c1b8f8e1e8af997e678c7b81"},
+    {file = "frozenlist-1.3.3-cp311-cp311-win32.whl", hash = "sha256:f30f1928162e189091cf4d9da2eac617bfe78ef907a761614ff577ef4edfb3c8"},
+    {file = "frozenlist-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:a6394d7dadd3cfe3f4b3b186e54d5d8504d44f2d58dcc89d693698e8b7132b32"},
+    {file = "frozenlist-1.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8df3de3a9ab8325f94f646609a66cbeeede263910c5c0de0101079ad541af332"},
+    {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0693c609e9742c66ba4870bcee1ad5ff35462d5ffec18710b4ac89337ff16e27"},
+    {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd4210baef299717db0a600d7a3cac81d46ef0e007f88c9335db79f8979c0d3d"},
+    {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:394c9c242113bfb4b9aa36e2b80a05ffa163a30691c7b5a29eba82e937895d5e"},
+    {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6327eb8e419f7d9c38f333cde41b9ae348bec26d840927332f17e887a8dcb70d"},
+    {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e24900aa13212e75e5b366cb9065e78bbf3893d4baab6052d1aca10d46d944c"},
+    {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3843f84a6c465a36559161e6c59dce2f2ac10943040c2fd021cfb70d58c4ad56"},
+    {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:84610c1502b2461255b4c9b7d5e9c48052601a8957cd0aea6ec7a7a1e1fb9420"},
+    {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c21b9aa40e08e4f63a2f92ff3748e6b6c84d717d033c7b3438dd3123ee18f70e"},
+    {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:efce6ae830831ab6a22b9b4091d411698145cb9b8fc869e1397ccf4b4b6455cb"},
+    {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:40de71985e9042ca00b7953c4f41eabc3dc514a2d1ff534027f091bc74416401"},
+    {file = "frozenlist-1.3.3-cp37-cp37m-win32.whl", hash = "sha256:180c00c66bde6146a860cbb81b54ee0df350d2daf13ca85b275123bbf85de18a"},
+    {file = "frozenlist-1.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9bbbcedd75acdfecf2159663b87f1bb5cfc80e7cd99f7ddd9d66eb98b14a8411"},
+    {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:034a5c08d36649591be1cbb10e09da9f531034acfe29275fc5454a3b101ce41a"},
+    {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba64dc2b3b7b158c6660d49cdb1d872d1d0bf4e42043ad8d5006099479a194e5"},
+    {file = "frozenlist-1.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47df36a9fe24054b950bbc2db630d508cca3aa27ed0566c0baf661225e52c18e"},
+    {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:008a054b75d77c995ea26629ab3a0c0d7281341f2fa7e1e85fa6153ae29ae99c"},
+    {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:841ea19b43d438a80b4de62ac6ab21cfe6827bb8a9dc62b896acc88eaf9cecba"},
+    {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e235688f42b36be2b6b06fc37ac2126a73b75fb8d6bc66dd632aa35286238703"},
+    {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca713d4af15bae6e5d79b15c10c8522859a9a89d3b361a50b817c98c2fb402a2"},
+    {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ac5995f2b408017b0be26d4a1d7c61bce106ff3d9e3324374d66b5964325448"},
+    {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4ae8135b11652b08a8baf07631d3ebfe65a4c87909dbef5fa0cdde440444ee4"},
+    {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ea42116ceb6bb16dbb7d526e242cb6747b08b7710d9782aa3d6732bd8d27649"},
+    {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:810860bb4bdce7557bc0febb84bbd88198b9dbc2022d8eebe5b3590b2ad6c842"},
+    {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ee78feb9d293c323b59a6f2dd441b63339a30edf35abcb51187d2fc26e696d13"},
+    {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0af2e7c87d35b38732e810befb9d797a99279cbb85374d42ea61c1e9d23094b3"},
+    {file = "frozenlist-1.3.3-cp38-cp38-win32.whl", hash = "sha256:899c5e1928eec13fd6f6d8dc51be23f0d09c5281e40d9cf4273d188d9feeaf9b"},
+    {file = "frozenlist-1.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:7f44e24fa70f6fbc74aeec3e971f60a14dde85da364aa87f15d1be94ae75aeef"},
+    {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2b07ae0c1edaa0a36339ec6cce700f51b14a3fc6545fdd32930d2c83917332cf"},
+    {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ebb86518203e12e96af765ee89034a1dbb0c3c65052d1b0c19bbbd6af8a145e1"},
+    {file = "frozenlist-1.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5cf820485f1b4c91e0417ea0afd41ce5cf5965011b3c22c400f6d144296ccbc0"},
+    {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c11e43016b9024240212d2a65043b70ed8dfd3b52678a1271972702d990ac6d"},
+    {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8fa3c6e3305aa1146b59a09b32b2e04074945ffcfb2f0931836d103a2c38f936"},
+    {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:352bd4c8c72d508778cf05ab491f6ef36149f4d0cb3c56b1b4302852255d05d5"},
+    {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65a5e4d3aa679610ac6e3569e865425b23b372277f89b5ef06cf2cdaf1ebf22b"},
+    {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e2c1185858d7e10ff045c496bbf90ae752c28b365fef2c09cf0fa309291669"},
+    {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f163d2fd041c630fed01bc48d28c3ed4a3b003c00acd396900e11ee5316b56bb"},
+    {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05cdb16d09a0832eedf770cb7bd1fe57d8cf4eaf5aced29c4e41e3f20b30a784"},
+    {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8bae29d60768bfa8fb92244b74502b18fae55a80eac13c88eb0b496d4268fd2d"},
+    {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eedab4c310c0299961ac285591acd53dc6723a1ebd90a57207c71f6e0c2153ab"},
+    {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3bbdf44855ed8f0fbcd102ef05ec3012d6a4fd7c7562403f76ce6a52aeffb2b1"},
+    {file = "frozenlist-1.3.3-cp39-cp39-win32.whl", hash = "sha256:efa568b885bca461f7c7b9e032655c0c143d305bf01c30caf6db2854a4532b38"},
+    {file = "frozenlist-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9"},
+    {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"},
 ]
 fsspec = [
     {file = "fsspec-2022.10.0-py3-none-any.whl", hash = "sha256:6b7c6ab3b476cdf17efcfeccde7fca28ef5a48f73a71010aaceec5fc15bf9ebf"},
@@ -1293,8 +1308,8 @@ pep517 = [
     {file = "pep517-0.13.0.tar.gz", hash = "sha256:ae69927c5c172be1add9203726d4b84cf3ebad1edcd5f71fcdc746e66e829f59"},
 ]
 platformdirs = [
-    {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
-    {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
+    {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"},
+    {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"},
 ]
 pluggy = [
     {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
@@ -1499,8 +1514,8 @@ s3fs = [
     {file = "s3fs-2022.10.0.tar.gz", hash = "sha256:e8deb80f20bd0b2059141b874fdb9d6aeb8cce35312ea5f2c02b225a78a00406"},
 ]
 setuptools = [
-    {file = "setuptools-65.5.0-py3-none-any.whl", hash = "sha256:f62ea9da9ed6289bfe868cd6845968a2c854d1427f8548d52cae02a42b4f0356"},
-    {file = "setuptools-65.5.0.tar.gz", hash = "sha256:512e5536220e38146176efb833d4a62aa726b7bbff82cfbc8ba9eaa3996e0b17"},
+    {file = "setuptools-65.5.1-py3-none-any.whl", hash = "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31"},
+    {file = "setuptools-65.5.1.tar.gz", hash = "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"},
 ]
 six = [
     {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
@@ -1526,8 +1541,8 @@ urllib3 = [
     {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
 ]
 virtualenv = [
-    {file = "virtualenv-20.16.6-py3-none-any.whl", hash = "sha256:186ca84254abcbde98180fd17092f9628c5fe742273c02724972a1d8a2035108"},
-    {file = "virtualenv-20.16.6.tar.gz", hash = "sha256:530b850b523c6449406dfba859d6345e48ef19b8439606c5d74d7d3c9e14d76e"},
+    {file = "virtualenv-20.16.7-py3-none-any.whl", hash = "sha256:efd66b00386fdb7dbe4822d172303f40cd05e50e01740b19ea42425cbe653e29"},
+    {file = "virtualenv-20.16.7.tar.gz", hash = "sha256:8691e3ff9387f743e00f6bb20f70121f5e4f596cae754531f2b3b3a1b1ac696e"},
 ]
 wrapt = [
     {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"},
diff --git a/python/pyiceberg/expressions/__init__.py b/python/pyiceberg/expressions/__init__.py
index 39d4884a51..d6ae395c17 100644
--- a/python/pyiceberg/expressions/__init__.py
+++ b/python/pyiceberg/expressions/__init__.py
@@ -18,18 +18,37 @@
 from __future__ import annotations
 
 from abc import ABC, abstractmethod
-from dataclasses import dataclass
 from functools import reduce
-from typing import ClassVar, Generic, TypeVar
-
-from pyiceberg.expressions.literals import Literal
+from typing import (
+    Any,
+    Generic,
+    Iterable,
+    Set,
+    Type,
+    Union,
+)
+
+from pyiceberg.expressions.literals import Literal, literal
 from pyiceberg.files import StructProtocol
 from pyiceberg.schema import Accessor, Schema
+from pyiceberg.typedef import L
 from pyiceberg.types import DoubleType, FloatType, NestedField
 from pyiceberg.utils.singleton import Singleton
 
-T = TypeVar("T")
-B = TypeVar("B")
+
+def _to_unbound_term(term: Union[str, UnboundTerm]) -> UnboundTerm:
+    return Reference(term) if isinstance(term, str) else term
+
+
+def _to_literal_set(values: Union[Iterable[L], Iterable[Literal[L]]]) -> Set[Literal[L]]:
+    return {_to_literal(v) for v in values}
+
+
+def _to_literal(value: Union[L, Literal[L]]) -> Literal[L]:
+    if isinstance(value, Literal):
+        return value
+    else:
+        return literal(value)
 
 
 class BooleanExpression(ABC):
@@ -40,7 +59,7 @@ class BooleanExpression(ABC):
         """Transform the Expression into its negated version."""
 
 
-class Term(Generic[T], ABC):
+class Term(Generic[L], ABC):
     """A simple expression that evaluates to a value"""
 
 
@@ -48,28 +67,28 @@ class Bound(ABC):
     """Represents a bound value expression"""
 
 
-class Unbound(Generic[B], ABC):
+class Unbound(ABC):
     """Represents an unbound value expression"""
 
+    @property
     @abstractmethod
-    def bind(self, schema: Schema, case_sensitive: bool = True) -> B:
-        ...  # pragma: no cover
+    def as_bound(self) -> Type[Bound]:
+        ...
 
 
-class BoundTerm(Term[T], Bound, ABC):
+class BoundTerm(Term[L], Bound, ABC):
     """Represents a bound term"""
 
     @abstractmethod
-    def ref(self) -> BoundReference[T]:
+    def ref(self) -> BoundReference[L]:
         """Returns the bound reference"""
 
     @abstractmethod
-    def eval(self, struct: StructProtocol) -> T:  # pylint: disable=W0613
+    def eval(self, struct: StructProtocol) -> L:  # pylint: disable=W0613
         """Returns the value at the referenced field's position in an object that abides by the StructProtocol"""
 
 
-@dataclass(frozen=True)
-class BoundReference(BoundTerm[T]):
+class BoundReference(BoundTerm[L]):
     """A reference bound to a field in a schema
 
     Args:
@@ -80,7 +99,11 @@ class BoundReference(BoundTerm[T]):
     field: NestedField
     accessor: Accessor
 
-    def eval(self, struct: StructProtocol) -> T:
+    def __init__(self, field: NestedField, accessor: Accessor):
+        self.field = field
+        self.accessor = accessor
+
+    def eval(self, struct: StructProtocol) -> L:
         """Returns the value at the referenced field's position in an object that abides by the StructProtocol
 
         Args:
@@ -90,16 +113,25 @@ class BoundReference(BoundTerm[T]):
         """
         return self.accessor.get(struct)
 
-    def ref(self) -> BoundReference[T]:
+    def __eq__(self, other):
+        return self.field == other.field if isinstance(other, BoundReference) else False
+
+    def __repr__(self) -> str:
+        return f"BoundReference(field={repr(self.field)}, accessor={repr(self.accessor)})"
+
+    def ref(self) -> BoundReference[L]:
         return self
 
 
-class UnboundTerm(Term[T], Unbound[BoundTerm[T]], ABC):
+class UnboundTerm(Term, Unbound, ABC):
     """Represents an unbound term."""
 
+    @abstractmethod
+    def bind(self, schema: Schema, case_sensitive: bool = True) -> BoundTerm:
+        ...  # pragma: no cover
+
 
-@dataclass(frozen=True)
-class Reference(UnboundTerm[T]):
+class Reference(UnboundTerm):
     """A reference not yet bound to a field in a schema
 
     Args:
@@ -111,7 +143,16 @@ class Reference(UnboundTerm[T]):
 
     name: str
 
-    def bind(self, schema: Schema, case_sensitive: bool = True) -> BoundReference[T]:
+    def __init__(self, name: str):
+        self.name = name
+
+    def __repr__(self):
+        return f"Reference(name={repr(self.name)})"
+
+    def __eq__(self, other):
+        return self.name == other.name if isinstance(other, Reference) else False
+
+    def bind(self, schema: Schema, case_sensitive: bool = True) -> BoundReference[L]:
         """Bind the reference to an Iceberg schema
 
         Args:
@@ -126,10 +167,13 @@ class Reference(UnboundTerm[T]):
         """
         field = schema.find_field(name_or_id=self.name, case_sensitive=case_sensitive)
         accessor = schema.accessor_for_field(field.field_id)
-        return BoundReference(field=field, accessor=accessor)
+        return self.as_bound(field=field, accessor=accessor)
+
+    @property
+    def as_bound(self) -> Type[BoundReference]:
+        return BoundReference[L]
 
 
-@dataclass(frozen=True, init=False)
 class And(BooleanExpression):
     """AND operation expression - logical conjunction"""
 
@@ -146,16 +190,24 @@ class And(BooleanExpression):
         elif right is AlwaysTrue():
             return left
         else:
-            result = super().__new__(cls)
-            object.__setattr__(result, "left", left)
-            object.__setattr__(result, "right", right)
-            return result
+            obj = super().__new__(cls)
+            obj.left = left
+            obj.right = right
+            return obj
+
+    def __eq__(self, other: Any) -> bool:
+        return self.left == other.left and self.right == other.right if isinstance(other, And) else False
+
+    def __str__(self) -> str:
+        return f"And(left={str(self.left)}, right={str(self.right)})"
+
+    def __repr__(self) -> str:
+        return f"And(left={repr(self.left)}, right={repr(self.right)})"
 
     def __invert__(self) -> Or:
         return Or(~self.left, ~self.right)
 
 
-@dataclass(frozen=True, init=False)
 class Or(BooleanExpression):
     """OR operation expression - logical disjunction"""
 
@@ -172,16 +224,21 @@ class Or(BooleanExpression):
         elif right is AlwaysFalse():
             return left
         else:
-            result = super().__new__(cls)
-            object.__setattr__(result, "left", left)
-            object.__setattr__(result, "right", right)
-            return result
+            obj = super().__new__(cls)
+            obj.left = left
+            obj.right = right
+            return obj
+
+    def __eq__(self, other: Any) -> bool:
+        return self.left == other.left and self.right == other.right if isinstance(other, Or) else False
+
+    def __repr__(self) -> str:
+        return f"Or(left={repr(self.left)}, right={repr(self.right)})"
 
     def __invert__(self) -> And:
         return And(~self.left, ~self.right)
 
 
-@dataclass(frozen=True, init=False)
 class Not(BooleanExpression):
     """NOT operation expression - logical negation"""
 
@@ -194,85 +251,108 @@ class Not(BooleanExpression):
             return AlwaysTrue()
         elif isinstance(child, Not):
             return child.child
-        result = super().__new__(cls)
-        object.__setattr__(result, "child", child)
-        return result
+        obj = super().__new__(cls)
+        obj.child = child
+        return obj
+
+    def __repr__(self) -> str:
+        return f"Not(child={repr(self.child)})"
+
+    def __eq__(self, other: Any) -> bool:
+        return self.child == other.child if isinstance(other, Not) else False
 
     def __invert__(self) -> BooleanExpression:
         return self.child
 
 
-@dataclass(frozen=True)
 class AlwaysTrue(BooleanExpression, Singleton):
     """TRUE expression"""
 
     def __invert__(self) -> AlwaysFalse:
         return AlwaysFalse()
 
+    def __str__(self):
+        return "AlwaysTrue()"
+
+    def __repr__(self):
+        return "AlwaysTrue()"
+
 
-@dataclass(frozen=True)
 class AlwaysFalse(BooleanExpression, Singleton):
     """FALSE expression"""
 
     def __invert__(self) -> AlwaysTrue:
         return AlwaysTrue()
 
+    def __str__(self):
+        return "AlwaysFalse()"
+
+    def __repr__(self):
+        return "AlwaysFalse()"
+
 
-@dataclass(frozen=True)
-class BoundPredicate(Generic[T], Bound, BooleanExpression):
-    term: BoundTerm[T]
+class BoundPredicate(Generic[L], Bound, BooleanExpression, ABC):
+    term: BoundTerm[L]
 
-    def __invert__(self) -> BoundPredicate[T]:
-        """Inverts the predicate"""
-        raise NotImplementedError
+    def __init__(self, term: BoundTerm[L]):
+        self.term = term
 
+    def __eq__(self, other: Any) -> bool:
+        if isinstance(other, BoundPredicate):
+            return self.term == other.term
+        return False
 
-@dataclass(frozen=True)
-class UnboundPredicate(Generic[T], Unbound[BooleanExpression], BooleanExpression):
-    as_bound: ClassVar[type]
-    term: UnboundTerm[T]
 
-    def __invert__(self) -> UnboundPredicate[T]:
-        """Inverts the predicate"""
-        raise NotImplementedError
+class UnboundPredicate(Generic[L], Unbound, BooleanExpression, ABC):
+    term: UnboundTerm
 
-    def bind(self, schema: Schema, case_sensitive: bool = True) -> BooleanExpression:
-        """Binds the predicate to a schema"""
-        raise NotImplementedError
+    def __init__(self, term: Union[str, UnboundTerm]):
+        self.term = _to_unbound_term(term)
 
+    def __eq__(self, other):
+        return self.term == other.term if isinstance(other, UnboundPredicate) else False
 
-@dataclass(frozen=True)
-class UnaryPredicate(UnboundPredicate[T]):
-    def bind(self, schema: Schema, case_sensitive: bool = True) -> BooleanExpression:
+    @abstractmethod
+    def bind(self, schema: Schema, case_sensitive: bool = True) -> BoundPredicate:
+        ...
+
+    @property
+    @abstractmethod
+    def as_bound(self) -> Type[BoundPredicate]:
+        ...
+
+
+class UnaryPredicate(UnboundPredicate[Any], ABC):
+    def bind(self, schema: Schema, case_sensitive: bool = True) -> BoundUnaryPredicate:
         bound_term = self.term.bind(schema, case_sensitive)
         return self.as_bound(bound_term)
 
-    def __invert__(self) -> UnaryPredicate[T]:
-        """Inverts the unary predicate"""
-        raise NotImplementedError
+    def __repr__(self) -> str:
+        return f"{str(self.__class__.__name__)}(term={repr(self.term)})"
+
+    @property
+    @abstractmethod
+    def as_bound(self) -> Type[BoundUnaryPredicate[L]]:
+        ...
 
 
-@dataclass(frozen=True)
-class BoundUnaryPredicate(BoundPredicate[T]):
-    def __invert__(self) -> BoundUnaryPredicate[T]:
-        """Inverts the unary predicate"""
-        raise NotImplementedError
+class BoundUnaryPredicate(BoundPredicate[L], ABC):
+    def __repr__(self) -> str:
+        return f"{str(self.__class__.__name__)}(term={repr(self.term)})"
 
 
-@dataclass(frozen=True)
-class BoundIsNull(BoundUnaryPredicate[T]):
-    def __new__(cls, term: BoundTerm[T]):  # pylint: disable=W0221
+class BoundIsNull(BoundUnaryPredicate[L]):
+    def __new__(cls, term: BoundTerm[L]):  # pylint: disable=W0221
         if term.ref().field.required:
             return AlwaysFalse()
         return super().__new__(cls)
 
-    def __invert__(self) -> BoundNotNull[T]:
+    def __invert__(self) -> BoundNotNull[L]:
         return BoundNotNull(self.term)
 
 
-@dataclass(frozen=True)
-class BoundNotNull(BoundUnaryPredicate[T]):
-    def __new__(cls, term: BoundTerm[T]):  # pylint: disable=W0221
+class BoundNotNull(BoundUnaryPredicate[L]):
+    def __new__(cls, term: BoundTerm[L]):  # pylint: disable=W0221
         if term.ref().field.required:
             return AlwaysTrue()
         return super().__new__(cls)
@@ -281,87 +361,114 @@ class BoundNotNull(BoundUnaryPredicate[T]):
         return BoundIsNull(self.term)
 
 
-@dataclass(frozen=True)
-class IsNull(UnaryPredicate[T]):
-    as_bound = BoundIsNull
-
-    def __invert__(self) -> NotNull[T]:
+class IsNull(UnaryPredicate):
+    def __invert__(self) -> NotNull:
         return NotNull(self.term)
 
+    @property
+    def as_bound(self) -> Type[BoundIsNull[L]]:
+        return BoundIsNull[L]
 
-@dataclass(frozen=True)
-class NotNull(UnaryPredicate[T]):
-    as_bound = BoundNotNull
 
-    def __invert__(self) -> IsNull[T]:
+class NotNull(UnaryPredicate):
+    def __invert__(self) -> IsNull:
         return IsNull(self.term)
 
+    @property
+    def as_bound(self) -> Type[BoundNotNull[L]]:
+        return BoundNotNull[L]
+
 
-@dataclass(frozen=True)
-class BoundIsNaN(BoundUnaryPredicate[T]):
-    def __new__(cls, term: BoundTerm[T]):  # pylint: disable=W0221
+class BoundIsNaN(BoundUnaryPredicate[L]):
+    def __new__(cls, term: BoundTerm[L]):  # pylint: disable=W0221
         bound_type = term.ref().field.field_type
         if type(bound_type) in {FloatType, DoubleType}:
             return super().__new__(cls)
         return AlwaysFalse()
 
-    def __invert__(self) -> BoundNotNaN[T]:
+    def __invert__(self) -> BoundNotNaN[L]:
         return BoundNotNaN(self.term)
 
 
-@dataclass(frozen=True)
-class BoundNotNaN(BoundUnaryPredicate[T]):
-    def __new__(cls, term: BoundTerm[T]):  # pylint: disable=W0221
+class BoundNotNaN(BoundUnaryPredicate[L]):
+    def __new__(cls, term: BoundTerm[L]):  # pylint: disable=W0221
         bound_type = term.ref().field.field_type
         if type(bound_type) in {FloatType, DoubleType}:
             return super().__new__(cls)
         return AlwaysTrue()
 
-    def __invert__(self) -> BoundIsNaN[T]:
+    def __invert__(self) -> BoundIsNaN[L]:
         return BoundIsNaN(self.term)
 
 
-@dataclass(frozen=True)
-class IsNaN(UnaryPredicate[T]):
-    as_bound = BoundIsNaN
-
-    def __invert__(self) -> NotNaN[T]:
+class IsNaN(UnaryPredicate):
+    def __invert__(self) -> NotNaN:
         return NotNaN(self.term)
 
+    @property
+    def as_bound(self) -> Type[BoundIsNaN[L]]:
+        return BoundIsNaN[L]
 
-@dataclass(frozen=True)
-class NotNaN(UnaryPredicate[T]):
-    as_bound = BoundNotNaN
 
-    def __invert__(self) -> IsNaN[T]:
+class NotNaN(UnaryPredicate):
+    def __invert__(self) -> IsNaN:
         return IsNaN(self.term)
 
+    @property
+    def as_bound(self) -> Type[BoundNotNaN[L]]:
+        return BoundNotNaN[L]
+
 
-@dataclass(frozen=True)
-class SetPredicate(UnboundPredicate[T]):
-    literals: tuple[Literal[T], ...]
+class SetPredicate(Generic[L], UnboundPredicate[L], ABC):
+    literals: Set[Literal[L]]
 
-    def __invert__(self) -> SetPredicate[T]:
-        """Inverted expression of the SetPredicate"""
-        raise NotImplementedError
+    def __init__(self, term: Union[str, UnboundTerm], literals: Union[Iterable[L], Iterable[Literal[L]]]):
+        super().__init__(term)
+        self.literals = _to_literal_set(literals)
 
-    def bind(self, schema: Schema, case_sensitive: bool = True) -> BooleanExpression:
+    def bind(self, schema: Schema, case_sensitive: bool = True) -> BoundSetPredicate[L]:
         bound_term = self.term.bind(schema, case_sensitive)
         return self.as_bound(bound_term, {lit.to(bound_term.ref().field.field_type) for lit in self.literals})
 
+    def __str__(self):
+        # Sort to make it deterministic
+        return f"{str(self.__class__.__name__)}({str(self.term)}, {{{', '.join(sorted([str(literal) for literal in self.literals]))}}})"
+
+    def __repr__(self) -> str:
+        # Sort to make it deterministic
+        return f"{str(self.__class__.__name__)}({repr(self.term)}, {{{', '.join(sorted([repr(literal) for literal in self.literals]))}}})"
+
+    def __eq__(self, other: Any) -> bool:
+        return self.term == other.term and self.literals == other.literals if isinstance(other, SetPredicate) else False
+
+    @property
+    @abstractmethod
+    def as_bound(self) -> Type[BoundSetPredicate[L]]:
+        return BoundSetPredicate[L]
+
+
+class BoundSetPredicate(BoundPredicate[L], ABC):
+    literals: Set[Literal[L]]
+
+    def __init__(self, term: BoundTerm[L], literals: Set[Literal[L]]):
+        # Since we don't know the type of BoundPredicate[L], we have to ignore this one
+        super().__init__(term)  # type: ignore
+        self.literals = _to_literal_set(literals)  # pylint: disable=W0621
 
-@dataclass(frozen=True)
-class BoundSetPredicate(BoundPredicate[T]):
-    literals: set[Literal[T]]
+    def __str__(self):
+        # Sort to make it deterministic
+        return f"{str(self.__class__.__name__)}({str(self.term)}, {{{', '.join(sorted([str(literal) for literal in self.literals]))}}})"
 
-    def __invert__(self) -> BoundSetPredicate[T]:
-        """Inverted expression of the SetPredicate"""
-        raise NotImplementedError
+    def __repr__(self) -> str:
+        # Sort to make it deterministic
+        return f"{str(self.__class__.__name__)}({repr(self.term)}, {{{', '.join(sorted([repr(literal) for literal in self.literals]))}}})"
 
+    def __eq__(self, other: Any) -> bool:
+        return self.term == other.term and self.literals == other.literals if isinstance(other, BoundSetPredicate) else False
 
-@dataclass(frozen=True)
-class BoundIn(BoundSetPredicate[T]):
-    def __new__(cls, term: BoundTerm[T], literals: set[Literal[T]]):  # pylint: disable=W0221
+
+class BoundIn(BoundSetPredicate[L]):
+    def __new__(cls, term: BoundTerm[L], literals: Set[Literal[L]]):  # pylint: disable=W0221
         count = len(literals)
         if count == 0:
             return AlwaysFalse()
@@ -370,13 +477,19 @@ class BoundIn(BoundSetPredicate[T]):
         else:
             return super().__new__(cls)
 
-    def __invert__(self) -> BoundNotIn[T]:
+    def __invert__(self) -> BoundNotIn[L]:
         return BoundNotIn(self.term, self.literals)
 
+    def __eq__(self, other: Any) -> bool:
+        return self.term == other.term and self.literals == other.literals if isinstance(other, BoundIn) else False
+
 
-@dataclass(frozen=True)
-class BoundNotIn(BoundSetPredicate[T]):
-    def __new__(cls, term: BoundTerm[T], literals: set[Literal[T]]):  # pylint: disable=W0221
+class BoundNotIn(BoundSetPredicate[L]):
+    def __new__(  # pylint: disable=W0221
+        cls,
+        term: BoundTerm[L],
+        literals: Set[Literal[L]],
+    ):
         count = len(literals)
         if count == 0:
             return AlwaysTrue()
@@ -385,145 +498,174 @@ class BoundNotIn(BoundSetPredicate[T]):
         else:
             return super().__new__(cls)
 
-    def __invert__(self) -> BoundIn[T]:
+    def __invert__(self) -> BoundIn[L]:
         return BoundIn(self.term, self.literals)
 
 
-@dataclass(frozen=True)
-class In(SetPredicate[T]):
-    as_bound = BoundIn
-
-    def __new__(cls, term: UnboundTerm[T], literals: tuple[Literal[T], ...]):  # pylint: disable=W0221
-        count = len(literals)
+class In(SetPredicate[L]):
+    def __new__(cls, term: Union[str, UnboundTerm], literals: Union[Iterable[L], Iterable[Literal[L]]]):  # pylint: disable=W0221
+        literals_set: Set[Literal[L]] = _to_literal_set(literals)
+        count = len(literals_set)
         if count == 0:
             return AlwaysFalse()
         elif count == 1:
-            return EqualTo(term, literals[0])
+            return EqualTo(term, next(iter(literals)))
         else:
             return super().__new__(cls)
 
-    def __invert__(self) -> NotIn[T]:
-        return NotIn(self.term, self.literals)
+    def __invert__(self) -> NotIn:
+        return NotIn[L](self.term, self.literals)
 
+    @property
+    def as_bound(self) -> Type[BoundIn[L]]:
+        return BoundIn[L]
 
-@dataclass(frozen=True)
-class NotIn(SetPredicate[T]):
-    as_bound = BoundNotIn
 
-    def __new__(cls, term: UnboundTerm[T], literals: tuple[Literal[T], ...]):  # pylint: disable=W0221
-        count = len(literals)
+class NotIn(SetPredicate[L], ABC):
+    def __new__(cls, term: Union[str, UnboundTerm], literals: Union[Iterable[L], Iterable[Literal[L]]]):  # pylint: disable=W0221
+        literals_set: Set[Literal[L]] = _to_literal_set(literals)
+        count = len(literals_set)
         if count == 0:
             return AlwaysTrue()
         elif count == 1:
-            return NotEqualTo(term, literals[0])
+            return NotEqualTo(term, next(iter(literals_set)))
         else:
             return super().__new__(cls)
 
-    def __invert__(self) -> In[T]:
-        return In(self.term, self.literals)
+    def __invert__(self) -> In:
+        return In[L](self.term, self.literals)
+
+    def __eq__(self, other: Any) -> bool:
+        if isinstance(other, NotIn):
+            return self.term == other.term and self.literals == other.literals
+        return False
+
+    @property
+    def as_bound(self) -> Type[BoundNotIn[L]]:
+        return BoundNotIn[L]
+
 
+class LiteralPredicate(Generic[L], UnboundPredicate[L], ABC):
+    literal: Literal[L]
 
-@dataclass(frozen=True)
-class LiteralPredicate(UnboundPredicate[T]):
-    literal: Literal[T]
+    def __init__(self, term: Union[str, UnboundTerm], literal: Union[L, Literal[L]]):  # pylint: disable=W0621
+        super().__init__(term)
+        self.literal = _to_literal(literal)  # pylint: disable=W0621
 
-    def bind(self, schema: Schema, case_sensitive: bool = True) -> BooleanExpression:
+    def bind(self, schema: Schema, case_sensitive: bool = True) -> BoundLiteralPredicate[L]:
         bound_term = self.term.bind(schema, case_sensitive)
         return self.as_bound(bound_term, self.literal.to(bound_term.ref().field.field_type))
 
-    def __invert__(self) -> LiteralPredicate[T]:
-        """Inverts the literal predicate"""
-        raise NotImplementedError
+    def __eq__(self, other):
+        if isinstance(other, LiteralPredicate):
+            return self.term == other.term and self.literal == other.literal
+        return False
 
+    def __repr__(self) -> str:
+        return f"{str(self.__class__.__name__)}(term={repr(self.term)}, literal={repr(self.literal)})"
 
-@dataclass(frozen=True)
-class BoundLiteralPredicate(BoundPredicate[T]):
-    literal: Literal[T]
+    @property
+    @abstractmethod
+    def as_bound(self) -> Type[BoundLiteralPredicate[L]]:
+        ...
+
+
+class BoundLiteralPredicate(BoundPredicate[L], ABC):
+    literal: Literal[L]
+
+    def __init__(self, term: BoundTerm[L], literal: Literal[L]):  # pylint: disable=W0621
+        # Since we don't know the type of BoundPredicate[L], we have to ignore this one
+        super().__init__(term)  # type: ignore
+        self.literal = literal  # pylint: disable=W0621
 
-    def __invert__(self) -> BoundLiteralPredicate[T]:
-        """Inverts the bound literal predicate"""
-        raise NotImplementedError
+    def __eq__(self, other):
+        if isinstance(other, BoundLiteralPredicate):
+            return self.term == other.term and self.literal == other.literal
+        return False
 
+    def __repr__(self) -> str:
+        return f"{str(self.__class__.__name__)}(term={repr(self.term)}, literal={repr(self.literal)})"
 
-@dataclass(frozen=True)
-class BoundEqualTo(BoundLiteralPredicate[T]):
-    def __invert__(self) -> BoundNotEqualTo[T]:
+
+class BoundEqualTo(BoundLiteralPredicate[L]):
+    def __invert__(self) -> BoundNotEqualTo[L]:
         return BoundNotEqualTo(self.term, self.literal)
 
 
-@dataclass(frozen=True)
-class BoundNotEqualTo(BoundLiteralPredicate[T]):
-    def __invert__(self) -> BoundEqualTo[T]:
+class BoundNotEqualTo(BoundLiteralPredicate[L]):
+    def __invert__(self) -> BoundEqualTo[L]:
         return BoundEqualTo(self.term, self.literal)
 
 
-@dataclass(frozen=True)
-class BoundGreaterThanOrEqual(BoundLiteralPredicate[T]):
-    def __invert__(self) -> BoundLessThan[T]:
+class BoundGreaterThanOrEqual(BoundLiteralPredicate[L]):
+    def __invert__(self) -> BoundLessThan[L]:
         return BoundLessThan(self.term, self.literal)
 
 
-@dataclass(frozen=True)
-class BoundGreaterThan(BoundLiteralPredicate[T]):
-    def __invert__(self) -> BoundLessThanOrEqual[T]:
+class BoundGreaterThan(BoundLiteralPredicate[L]):
+    def __invert__(self) -> BoundLessThanOrEqual[L]:
         return BoundLessThanOrEqual(self.term, self.literal)
 
 
-@dataclass(frozen=True)
-class BoundLessThan(BoundLiteralPredicate[T]):
-    def __invert__(self) -> BoundGreaterThanOrEqual[T]:
+class BoundLessThan(BoundLiteralPredicate[L]):
+    def __invert__(self) -> BoundGreaterThanOrEqual[L]:
         return BoundGreaterThanOrEqual(self.term, self.literal)
 
 
-@dataclass(frozen=True)
-class BoundLessThanOrEqual(BoundLiteralPredicate[T]):
-    def __invert__(self) -> BoundGreaterThan[T]:
+class BoundLessThanOrEqual(BoundLiteralPredicate[L]):
+    def __invert__(self) -> BoundGreaterThan[L]:
         return BoundGreaterThan(self.term, self.literal)
 
 
-@dataclass(frozen=True)
-class EqualTo(LiteralPredicate[T]):
-    as_bound = BoundEqualTo
-
-    def __invert__(self) -> NotEqualTo[T]:
+class EqualTo(LiteralPredicate[L]):
+    def __invert__(self) -> NotEqualTo:
         return NotEqualTo(self.term, self.literal)
 
+    @property
+    def as_bound(self) -> Type[BoundEqualTo[L]]:
+        return BoundEqualTo[L]
 
-@dataclass(frozen=True)
-class NotEqualTo(LiteralPredicate[T]):
-    as_bound = BoundNotEqualTo
 
-    def __invert__(self) -> EqualTo[T]:
+class NotEqualTo(LiteralPredicate[L]):
+    def __invert__(self) -> EqualTo:
         return EqualTo(self.term, self.literal)
 
+    @property
+    def as_bound(self) -> Type[BoundNotEqualTo[L]]:
+        return BoundNotEqualTo[L]
 
-@dataclass(frozen=True)
-class LessThan(LiteralPredicate[T]):
-    as_bound = BoundLessThan
 
-    def __invert__(self) -> GreaterThanOrEqual[T]:
+class LessThan(LiteralPredicate):
+    def __invert__(self) -> GreaterThanOrEqual:
         return GreaterThanOrEqual(self.term, self.literal)
 
+    @property
+    def as_bound(self) -> Type[BoundLessThan[L]]:
+        return BoundLessThan[L]
 
-@dataclass(frozen=True)
-class GreaterThanOrEqual(LiteralPredicate[T]):
-    as_bound = BoundGreaterThanOrEqual
 
-    def __invert__(self) -> LessThan[T]:
+class GreaterThanOrEqual(LiteralPredicate[L]):
+    def __invert__(self) -> LessThan:
         return LessThan(self.term, self.literal)
 
+    @property
+    def as_bound(self) -> Type[BoundGreaterThanOrEqual[L]]:
+        return BoundGreaterThanOrEqual[L]
 
-@dataclass(frozen=True)
-class GreaterThan(LiteralPredicate[T]):
-    as_bound = BoundGreaterThan
 
-    def __invert__(self) -> LessThanOrEqual[T]:
+class GreaterThan(LiteralPredicate[L]):
+    def __invert__(self) -> LessThanOrEqual:
         return LessThanOrEqual(self.term, self.literal)
 
+    @property
+    def as_bound(self) -> Type[BoundGreaterThan[L]]:
+        return BoundGreaterThan[L]
 
-@dataclass(frozen=True)
-class LessThanOrEqual(LiteralPredicate[T]):
-    as_bound = BoundLessThanOrEqual
 
-    def __invert__(self) -> GreaterThan[T]:
+class LessThanOrEqual(LiteralPredicate[L]):
+    def __invert__(self) -> GreaterThan:
         return GreaterThan(self.term, self.literal)
+
+    @property
+    def as_bound(self) -> Type[BoundLessThanOrEqual[L]]:
+        return BoundLessThanOrEqual[L]
diff --git a/python/pyiceberg/expressions/literals.py b/python/pyiceberg/expressions/literals.py
index a84cd9ae39..ada6fcc8eb 100644
--- a/python/pyiceberg/expressions/literals.py
+++ b/python/pyiceberg/expressions/literals.py
@@ -23,17 +23,12 @@ from __future__ import annotations
 
 import struct
 from abc import ABC, abstractmethod
-from datetime import date
 from decimal import ROUND_HALF_UP, Decimal
-from functools import singledispatch, singledispatchmethod
-from typing import (
-    Any,
-    Generic,
-    Type,
-    TypeVar,
-)
+from functools import singledispatchmethod
+from typing import Any, Generic, Type
 from uuid import UUID
 
+from pyiceberg.typedef import L
 from pyiceberg.types import (
     BinaryType,
     BooleanType,
@@ -53,7 +48,6 @@ from pyiceberg.types import (
 )
 from pyiceberg.utils.datetime import (
     date_str_to_days,
-    date_to_days,
     micros_to_days,
     time_to_micros,
     timestamp_to_micros,
@@ -61,19 +55,19 @@ from pyiceberg.utils.datetime import (
 )
 from pyiceberg.utils.singleton import Singleton
 
-T = TypeVar("T")
-
 
-class Literal(Generic[T], ABC):
+class Literal(Generic[L], ABC):
     """Literal which has a value and can be converted between types"""
 
-    def __init__(self, value: T, value_type: Type):
+    _value: L
+
+    def __init__(self, value: L, value_type: Type[L]):
         if value is None or not isinstance(value, value_type):
-            raise TypeError(f"Invalid literal value: {value} (not a {value_type})")
+            raise TypeError(f"Invalid literal value: {value!r} (not a {value_type})")
         self._value = value
 
     @property
-    def value(self) -> T:
+    def value(self) -> L:
         return self._value
 
     @singledispatchmethod
@@ -82,7 +76,7 @@ class Literal(Generic[T], ABC):
         ...  # pragma: no cover
 
     def __repr__(self) -> str:
-        return f"{type(self).__name__}({self.value})"
+        return f"{type(self).__name__}({self.value!r})"
 
     def __str__(self) -> str:
         return str(self.value)
@@ -90,7 +84,7 @@ class Literal(Generic[T], ABC):
     def __hash__(self) -> int:
         return hash(self.value)
 
-    def __eq__(self, other) -> bool:
+    def __eq__(self, other: Any) -> bool:
         return self.value == other.value
 
     def __ne__(self, other) -> bool:
@@ -109,11 +103,9 @@ class Literal(Generic[T], ABC):
         return self.value >= other.value
 
 
-@singledispatch
-def literal(value: Any) -> Literal:
+def literal(value: L) -> Literal[L]:
     """
     A generic Literal factory to construct an iceberg Literal based on python primitive data type
-    using dynamic overloading
 
     Args:
         value(python primitive type): the value to be associated with literal
@@ -123,54 +115,22 @@ def literal(value: Any) -> Literal:
         >>> literal(123)
         LongLiteral(123)
     """
-    raise TypeError(f"Invalid literal value: {repr(value)}")
-
-
-@literal.register(bool)
-def _(value: bool) -> Literal[bool]:
-    return BooleanLiteral(value)
-
-
-@literal.register(int)
-def _(value: int) -> Literal[int]:
-    return LongLiteral(value)
-
-
-@literal.register(float)
-def _(value: float) -> Literal[float]:
-    # expression binding can convert to FloatLiteral if needed
-    return DoubleLiteral(value)
-
-
-@literal.register(str)
-def _(value: str) -> Literal[str]:
-    return StringLiteral(value)
-
-
-@literal.register(UUID)
-def _(value: UUID) -> Literal[UUID]:
-    return UUIDLiteral(value)
-
-
-@literal.register(bytes)
-def _(value: bytes) -> Literal[bytes]:
-    # expression binding can convert to FixedLiteral if needed
-    return BinaryLiteral(value)
-
-
-@literal.register(bytearray)
-def _(value: bytearray) -> Literal[bytes]:
-    return BinaryLiteral(bytes(value))
-
-
-@literal.register(Decimal)
-def _(value: Decimal) -> Literal[Decimal]:
-    return DecimalLiteral(value)
-
-
-@literal.register(date)
-def _(value: date) -> Literal[int]:
-    return DateLiteral(date_to_days(value))
+    if isinstance(value, float):
+        return DoubleLiteral(value)
+    elif isinstance(value, bool):
+        return BooleanLiteral(value)  # type: ignore
+    elif isinstance(value, int):
+        return LongLiteral(value)
+    elif isinstance(value, str):
+        return StringLiteral(value)
+    elif isinstance(value, UUID):
+        return UUIDLiteral(value)
+    elif isinstance(value, bytes):
+        return BinaryLiteral(value)
+    elif isinstance(value, Decimal):
+        return DecimalLiteral(value)
+    else:
+        raise TypeError(f"Invalid literal value: {repr(value)}")
 
 
 class FloatAboveMax(Literal[float], Singleton):
@@ -481,6 +441,9 @@ class StringLiteral(Literal[str]):
                 f"Could not convert {self.value} into a {type_var}, scales differ {type_var.scale} <> {abs(dec.as_tuple().exponent)}"
             )
 
+    def __repr__(self) -> str:
+        return f"literal({repr(self.value)})"
+
 
 class UUIDLiteral(Literal[UUID]):
     def __init__(self, value: UUID):
diff --git a/python/pyiceberg/expressions/visitors.py b/python/pyiceberg/expressions/visitors.py
index 4c6df0b079..283e7d2317 100644
--- a/python/pyiceberg/expressions/visitors.py
+++ b/python/pyiceberg/expressions/visitors.py
@@ -17,11 +17,11 @@
 from abc import ABC, abstractmethod
 from functools import singledispatch
 from typing import (
-    Any,
     Callable,
     Generic,
     List,
     Set,
+    TypeVar,
 )
 
 from pyiceberg.conversions import from_bytes
@@ -44,9 +44,9 @@ from pyiceberg.expressions import (
     BoundNotNull,
     BoundPredicate,
     BoundTerm,
+    L,
     Not,
     Or,
-    T,
     UnboundPredicate,
 )
 from pyiceberg.expressions.literals import Literal
@@ -60,6 +60,8 @@ from pyiceberg.types import (
     PrimitiveType,
 )
 
+T = TypeVar("T")
+
 
 class BooleanExpressionVisitor(Generic[T], ABC):
     @abstractmethod
@@ -89,8 +91,8 @@ class BooleanExpressionVisitor(Generic[T], ABC):
         """Visit method for an And boolean expression
 
         Args:
-            left_result (T): The result of visiting the left side of the expression
-            right_result (T): The result of visiting the right side of the expression
+            left_result (R): The result of visiting the left side of the expression
+            right_result (R): The result of visiting the right side of the expression
         """
 
     @abstractmethod
@@ -98,12 +100,12 @@ class BooleanExpressionVisitor(Generic[T], ABC):
         """Visit method for an Or boolean expression
 
         Args:
-            left_result (T): The result of visiting the left side of the expression
-            right_result (T): The result of visiting the right side of the expression
+            left_result (R): The result of visiting the left side of the expression
+            right_result (R): The result of visiting the right side of the expression
         """
 
     @abstractmethod
-    def visit_unbound_predicate(self, predicate) -> T:
+    def visit_unbound_predicate(self, predicate: UnboundPredicate) -> T:
         """Visit method for an unbound predicate in an expression tree
 
         Args:
@@ -111,11 +113,11 @@ class BooleanExpressionVisitor(Generic[T], ABC):
         """
 
     @abstractmethod
-    def visit_bound_predicate(self, predicate) -> T:
+    def visit_bound_predicate(self, predicate: BoundPredicate[L]) -> T:
         """Visit method for a bound predicate in an expression tree
 
         Args:
-            predicate (BoundPredicate): An instance of a BoundPredicate
+            predicate (BoundPredicate[L]): An instance of a BoundPredicate
         """
 
 
@@ -127,7 +129,7 @@ def visit(obj, visitor: BooleanExpressionVisitor[T]) -> T:
 
     Args:
         obj(BooleanExpression): An instance of a BooleanExpression
-        visitor(BooleanExpressionVisitor[T]): An instance of an implementation of the generic BooleanExpressionVisitor base class
+        visitor(BooleanExpressionVisitor[R]): An instance of an implementation of the generic BooleanExpressionVisitor base class
 
     Raises:
         NotImplementedError: If attempting to visit an unsupported expression
@@ -135,26 +137,26 @@ def visit(obj, visitor: BooleanExpressionVisitor[T]) -> T:
     raise NotImplementedError(f"Cannot visit unsupported expression: {obj}")
 
 
-@visit.register
+@visit.register(AlwaysTrue)
 def _(_: AlwaysTrue, visitor: BooleanExpressionVisitor[T]) -> T:
     """Visit an AlwaysTrue boolean expression with a concrete BooleanExpressionVisitor"""
     return visitor.visit_true()
 
 
-@visit.register
+@visit.register(AlwaysFalse)
 def _(_: AlwaysFalse, visitor: BooleanExpressionVisitor[T]) -> T:
     """Visit an AlwaysFalse boolean expression with a concrete BooleanExpressionVisitor"""
     return visitor.visit_false()
 
 
-@visit.register
+@visit.register(Not)
 def _(obj: Not, visitor: BooleanExpressionVisitor[T]) -> T:
     """Visit a Not boolean expression with a concrete BooleanExpressionVisitor"""
     child_result: T = visit(obj.child, visitor=visitor)
     return visitor.visit_not(child_result=child_result)
 
 
-@visit.register
+@visit.register(And)
 def _(obj: And, visitor: BooleanExpressionVisitor[T]) -> T:
     """Visit an And boolean expression with a concrete BooleanExpressionVisitor"""
     left_result: T = visit(obj.left, visitor=visitor)
@@ -162,19 +164,19 @@ def _(obj: And, visitor: BooleanExpressionVisitor[T]) -> T:
     return visitor.visit_and(left_result=left_result, right_result=right_result)
 
 
-@visit.register
+@visit.register(UnboundPredicate)
 def _(obj: UnboundPredicate, visitor: BooleanExpressionVisitor[T]) -> T:
     """Visit an unbound boolean expression with a concrete BooleanExpressionVisitor"""
     return visitor.visit_unbound_predicate(predicate=obj)
 
 
-@visit.register
-def _(obj: BoundPredicate, visitor: BooleanExpressionVisitor[T]) -> T:
+@visit.register(BoundPredicate)
+def _(obj: BoundPredicate[L], visitor: BooleanExpressionVisitor[T]) -> T:
     """Visit a bound boolean expression with a concrete BooleanExpressionVisitor"""
     return visitor.visit_bound_predicate(predicate=obj)
 
 
-@visit.register
+@visit.register(Or)
 def _(obj: Or, visitor: BooleanExpressionVisitor[T]) -> T:
     """Visit an Or boolean expression with a concrete BooleanExpressionVisitor"""
     left_result: T = visit(obj.left, visitor=visitor)
@@ -232,57 +234,57 @@ class BindVisitor(BooleanExpressionVisitor[BooleanExpression]):
     def visit_unbound_predicate(self, predicate: UnboundPredicate) -> BooleanExpression:
         return predicate.bind(self.schema, case_sensitive=self.case_sensitive)
 
-    def visit_bound_predicate(self, predicate: BoundPredicate) -> BooleanExpression:
+    def visit_bound_predicate(self, predicate: BoundPredicate[L]) -> BooleanExpression:
         raise TypeError(f"Found already bound predicate: {predicate}")
 
 
 class BoundBooleanExpressionVisitor(BooleanExpressionVisitor[T], ABC):
     @abstractmethod
-    def visit_in(self, term: BoundTerm[T], literals: Set[Literal[Any]]) -> T:
+    def visit_in(self, term: BoundTerm[L], literals: Set[Literal[L]]) -> T:
         """Visit a bound In predicate"""
 
     @abstractmethod
-    def visit_not_in(self, term: BoundTerm[T], literals: Set[Literal[Any]]) -> T:
+    def visit_not_in(self, term: BoundTerm[L], literals: Set[Literal[L]]) -> T:
         """Visit a bound NotIn predicate"""
 
     @abstractmethod
-    def visit_is_nan(self, term: BoundTerm[T]) -> T:
+    def visit_is_nan(self, term: BoundTerm[L]) -> T:
         """Visit a bound IsNan predicate"""
 
     @abstractmethod
-    def visit_not_nan(self, term: BoundTerm[T]) -> T:
+    def visit_not_nan(self, term: BoundTerm[L]) -> T:
         """Visit a bound NotNan predicate"""
 
     @abstractmethod
-    def visit_is_null(self, term: BoundTerm[T]) -> T:
+    def visit_is_null(self, term: BoundTerm[L]) -> T:
         """Visit a bound IsNull predicate"""
 
     @abstractmethod
-    def visit_not_null(self, term: BoundTerm[T]) -> T:
+    def visit_not_null(self, term: BoundTerm[L]) -> T:
         """Visit a bound NotNull predicate"""
 
     @abstractmethod
-    def visit_equal(self, term: BoundTerm[T], literal: Literal[Any]) -> T:
+    def visit_equal(self, term: BoundTerm[L], literal: Literal[L]) -> T:
         """Visit a bound Equal predicate"""
 
     @abstractmethod
-    def visit_not_equal(self, term: BoundTerm[T], literal: Literal[Any]) -> T:
+    def visit_not_equal(self, term: BoundTerm[L], literal: Literal[L]) -> T:
         """Visit a bound NotEqual predicate"""
 
     @abstractmethod
-    def visit_greater_than_or_equal(self, term: BoundTerm[T], literal: Literal[Any]) -> T:
+    def visit_greater_than_or_equal(self, term: BoundTerm[L], literal: Literal[L]) -> T:
         """Visit a bound GreaterThanOrEqual predicate"""
 
     @abstractmethod
-    def visit_greater_than(self, term: BoundTerm[T], literal: Literal[Any]) -> T:
+    def visit_greater_than(self, term: BoundTerm[L], literal: Literal[L]) -> T:
         """Visit a bound GreaterThan predicate"""
 
     @abstractmethod
-    def visit_less_than(self, term: BoundTerm[T], literal: Literal[Any]) -> T:
+    def visit_less_than(self, term: BoundTerm[L], literal: Literal[L]) -> T:
         """Visit a bound LessThan predicate"""
 
     @abstractmethod
-    def visit_less_than_or_equal(self, term: BoundTerm[T], literal: Literal[Any]) -> T:
+    def visit_less_than_or_equal(self, term: BoundTerm[L], literal: Literal[L]) -> T:
         """Visit a bound LessThanOrEqual predicate"""
 
     @abstractmethod
@@ -305,86 +307,86 @@ class BoundBooleanExpressionVisitor(BooleanExpressionVisitor[T], ABC):
     def visit_or(self, left_result: T, right_result: T) -> T:
         """Visit a bound Or predicate"""
 
-    def visit_unbound_predicate(self, predicate: UnboundPredicate[T]):
+    def visit_unbound_predicate(self, predicate: UnboundPredicate):
         """Visit an unbound predicate
         Args:
-            predicate (UnboundPredicate[T]): An unbound predicate
+            predicate (UnboundPredicate): An unbound predicate
         Raises:
             TypeError: This always raises since an unbound predicate is not expected in a bound boolean expression
         """
         raise TypeError(f"Not a bound predicate: {predicate}")
 
-    def visit_bound_predicate(self, predicate: BoundPredicate[T]) -> T:
+    def visit_bound_predicate(self, predicate: BoundPredicate[L]) -> T:
         """Visit a bound predicate
         Args:
-            predicate (BoundPredicate[T]): A bound predicate
+            predicate (BoundPredicate[L]): A bound predicate
         """
         return visit_bound_predicate(predicate, self)
 
 
 @singledispatch
-def visit_bound_predicate(expr, _: BooleanExpressionVisitor[T]) -> T:
+def visit_bound_predicate(expr: BoundPredicate[L], _: BooleanExpressionVisitor[T]) -> T:
     raise TypeError(f"Unknown predicate: {expr}")
 
 
 @visit_bound_predicate.register(BoundIn)
-def _(expr: BoundIn, visitor: BoundBooleanExpressionVisitor[T]) -> T:
+def _(expr: BoundIn[L], visitor: BoundBooleanExpressionVisitor[T]) -> T:
     return visitor.visit_in(term=expr.term, literals=expr.literals)
 
 
 @visit_bound_predicate.register(BoundNotIn)
-def _(expr: BoundNotIn, visitor: BoundBooleanExpressionVisitor[T]) -> T:
+def _(expr: BoundNotIn[L], visitor: BoundBooleanExpressionVisitor[T]) -> T:
     return visitor.visit_not_in(term=expr.term, literals=expr.literals)
 
 
 @visit_bound_predicate.register(BoundIsNaN)
-def _(expr: BoundIsNaN, visitor: BoundBooleanExpressionVisitor[T]) -> T:
+def _(expr: BoundIsNaN[L], visitor: BoundBooleanExpressionVisitor[T]) -> T:
     return visitor.visit_is_nan(term=expr.term)
 
 
 @visit_bound_predicate.register(BoundNotNaN)
-def _(expr: BoundNotNaN, visitor: BoundBooleanExpressionVisitor[T]) -> T:
+def _(expr: BoundNotNaN[L], visitor: BoundBooleanExpressionVisitor[T]) -> T:
     return visitor.visit_not_nan(term=expr.term)
 
 
 @visit_bound_predicate.register(BoundIsNull)
-def _(expr: BoundIsNull, visitor: BoundBooleanExpressionVisitor[T]) -> T:
+def _(expr: BoundIsNull[L], visitor: BoundBooleanExpressionVisitor[T]) -> T:
     return visitor.visit_is_null(term=expr.term)
 
 
 @visit_bound_predicate.register(BoundNotNull)
-def _(expr: BoundNotNull, visitor: BoundBooleanExpressionVisitor[T]) -> T:
+def _(expr: BoundNotNull[L], visitor: BoundBooleanExpressionVisitor[T]) -> T:
     return visitor.visit_not_null(term=expr.term)
 
 
 @visit_bound_predicate.register(BoundEqualTo)
-def _(expr: BoundEqualTo, visitor: BoundBooleanExpressionVisitor[T]) -> T:
+def _(expr: BoundEqualTo[L], visitor: BoundBooleanExpressionVisitor[T]) -> T:
     return visitor.visit_equal(term=expr.term, literal=expr.literal)
 
 
 @visit_bound_predicate.register(BoundNotEqualTo)
-def _(expr: BoundNotEqualTo, visitor: BoundBooleanExpressionVisitor[T]) -> T:
+def _(expr: BoundNotEqualTo[L], visitor: BoundBooleanExpressionVisitor[T]) -> T:
     return visitor.visit_not_equal(term=expr.term, literal=expr.literal)
 
 
 @visit_bound_predicate.register(BoundGreaterThanOrEqual)
-def _(expr: BoundGreaterThanOrEqual, visitor: BoundBooleanExpressionVisitor[T]) -> T:
+def _(expr: BoundGreaterThanOrEqual[L], visitor: BoundBooleanExpressionVisitor[T]) -> T:
     """Visit a bound GreaterThanOrEqual predicate"""
     return visitor.visit_greater_than_or_equal(term=expr.term, literal=expr.literal)
 
 
 @visit_bound_predicate.register(BoundGreaterThan)
-def _(expr: BoundGreaterThan, visitor: BoundBooleanExpressionVisitor[T]) -> T:
+def _(expr: BoundGreaterThan[L], visitor: BoundBooleanExpressionVisitor[T]) -> T:
     return visitor.visit_greater_than(term=expr.term, literal=expr.literal)
 
 
 @visit_bound_predicate.register(BoundLessThan)
-def _(expr: BoundLessThan, visitor: BoundBooleanExpressionVisitor[T]) -> T:
+def _(expr: BoundLessThan[L], visitor: BoundBooleanExpressionVisitor[T]) -> T:
     return visitor.visit_less_than(term=expr.term, literal=expr.literal)
 
 
 @visit_bound_predicate.register(BoundLessThanOrEqual)
-def _(expr: BoundLessThanOrEqual, visitor: BoundBooleanExpressionVisitor[T]) -> T:
+def _(expr: BoundLessThanOrEqual[L], visitor: BoundBooleanExpressionVisitor[T]) -> T:
     return visitor.visit_less_than_or_equal(term=expr.term, literal=expr.literal)
 
 
@@ -410,10 +412,10 @@ class _RewriteNotVisitor(BooleanExpressionVisitor[BooleanExpression]):
     def visit_or(self, left_result: BooleanExpression, right_result: BooleanExpression) -> BooleanExpression:
         return Or(left=left_result, right=right_result)
 
-    def visit_unbound_predicate(self, predicate) -> BooleanExpression:
+    def visit_unbound_predicate(self, predicate: UnboundPredicate) -> BooleanExpression:
         return predicate
 
-    def visit_bound_predicate(self, predicate) -> BooleanExpression:
+    def visit_bound_predicate(self, predicate: BoundPredicate[L]) -> BooleanExpression:
         return predicate
 
 
@@ -443,7 +445,7 @@ class _ManifestEvalVisitor(BoundBooleanExpressionVisitor[bool]):
         # No partition information
         return ROWS_MIGHT_MATCH
 
-    def visit_in(self, term: BoundTerm, literals: Set[Literal[Any]]) -> bool:
+    def visit_in(self, term: BoundTerm[L], literals: Set[Literal[L]]) -> bool:
         pos = term.ref().accessor.position
         field = self.partition_fields[pos]
 
@@ -465,12 +467,12 @@ class _ManifestEvalVisitor(BoundBooleanExpressionVisitor[bool]):
 
         return ROWS_MIGHT_MATCH
 
-    def visit_not_in(self, term: BoundTerm, literals: Set[Literal[Any]]) -> bool:
+    def visit_not_in(self, term: BoundTerm[L], literals: Set[Literal[L]]) -> bool:
         # because the bounds are not necessarily a min or max value, this cannot be answered using
         # them. notIn(col, {X, ...}) with (X, Y) doesn't guarantee that X is a value in col.
         return ROWS_MIGHT_MATCH
 
-    def visit_is_nan(self, term: BoundTerm) -> bool:
+    def visit_is_nan(self, term: BoundTerm[L]) -> bool:
         pos = term.ref().accessor.position
         field = self.partition_fields[pos]
 
@@ -479,7 +481,7 @@ class _ManifestEvalVisitor(BoundBooleanExpressionVisitor[bool]):
 
         return ROWS_MIGHT_MATCH
 
-    def visit_not_nan(self, term: BoundTerm) -> bool:
+    def visit_not_nan(self, term: BoundTerm[L]) -> bool:
         pos = term.ref().accessor.position
         field = self.partition_fields[pos]
 
@@ -488,7 +490,7 @@ class _ManifestEvalVisitor(BoundBooleanExpressionVisitor[bool]):
 
         return ROWS_MIGHT_MATCH
 
-    def visit_is_null(self, term: BoundTerm) -> bool:
+    def visit_is_null(self, term: BoundTerm[L]) -> bool:
         pos = term.ref().accessor.position
 
         if self.partition_fields[pos].contains_null is False:
@@ -496,7 +498,7 @@ class _ManifestEvalVisitor(BoundBooleanExpressionVisitor[bool]):
 
         return ROWS_MIGHT_MATCH
 
-    def visit_not_null(self, term: BoundTerm) -> bool:
+    def visit_not_null(self, term: BoundTerm[L]) -> bool:
         pos = term.ref().accessor.position
 
         # contains_null encodes whether at least one partition value is null,
@@ -513,7 +515,7 @@ class _ManifestEvalVisitor(BoundBooleanExpressionVisitor[bool]):
 
         return ROWS_MIGHT_MATCH
 
-    def visit_equal(self, term: BoundTerm, literal: Literal[Any]) -> bool:
+    def visit_equal(self, term: BoundTerm[L], literal: Literal[L]) -> bool:
         pos = term.ref().accessor.position
         field = self.partition_fields[pos]
 
@@ -533,12 +535,12 @@ class _ManifestEvalVisitor(BoundBooleanExpressionVisitor[bool]):
 
         return ROWS_MIGHT_MATCH
 
-    def visit_not_equal(self, term: BoundTerm, literal: Literal[Any]) -> bool:
+    def visit_not_equal(self, term: BoundTerm[L], literal: Literal[L]) -> bool:
         # because the bounds are not necessarily a min or max value, this cannot be answered using
         # them. notEq(col, X) with (X, Y) doesn't guarantee that X is a value in col.
         return ROWS_MIGHT_MATCH
 
-    def visit_greater_than_or_equal(self, term: BoundTerm, literal: Literal[Any]) -> bool:
+    def visit_greater_than_or_equal(self, term: BoundTerm[L], literal: Literal[L]) -> bool:
         pos = term.ref().accessor.position
         field = self.partition_fields[pos]
 
@@ -552,7 +554,7 @@ class _ManifestEvalVisitor(BoundBooleanExpressionVisitor[bool]):
 
         return ROWS_MIGHT_MATCH
 
-    def visit_greater_than(self, term: BoundTerm, literal: Literal[Any]) -> bool:
+    def visit_greater_than(self, term: BoundTerm[L], literal: Literal[L]) -> bool:
         pos = term.ref().accessor.position
         field = self.partition_fields[pos]
 
@@ -566,7 +568,7 @@ class _ManifestEvalVisitor(BoundBooleanExpressionVisitor[bool]):
 
         return ROWS_MIGHT_MATCH
 
-    def visit_less_than(self, term: BoundTerm, literal: Literal[Any]) -> bool:
+    def visit_less_than(self, term: BoundTerm[L], literal: Literal[L]) -> bool:
         pos = term.ref().accessor.position
         field = self.partition_fields[pos]
 
@@ -580,7 +582,7 @@ class _ManifestEvalVisitor(BoundBooleanExpressionVisitor[bool]):
 
         return ROWS_MIGHT_MATCH
 
-    def visit_less_than_or_equal(self, term: BoundTerm, literal: Literal[Any]) -> bool:
+    def visit_less_than_or_equal(self, term: BoundTerm[L], literal: Literal[L]) -> bool:
         pos = term.ref().accessor.position
         field = self.partition_fields[pos]
 
diff --git a/python/pyiceberg/typedef.py b/python/pyiceberg/typedef.py
index 76add54bdd..b038bfb17c 100644
--- a/python/pyiceberg/typedef.py
+++ b/python/pyiceberg/typedef.py
@@ -14,13 +14,15 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
+from decimal import Decimal
 from typing import (
     Any,
     Dict,
     Tuple,
+    TypeVar,
     Union,
 )
+from uuid import UUID
 
 
 class FrozenDict(Dict[Any, Any]):
@@ -36,3 +38,6 @@ EMPTY_DICT = FrozenDict()
 Identifier = Tuple[str, ...]
 Properties = Dict[str, str]
 RecursiveDict = Dict[str, Union[str, "RecursiveDict"]]
+
+# Represents the literal value
+L = TypeVar("L", str, bool, int, float, bytes, UUID, Decimal)
diff --git a/python/pyproject.toml b/python/pyproject.toml
index b54f266e8b..181093e469 100644
--- a/python/pyproject.toml
+++ b/python/pyproject.toml
@@ -72,6 +72,7 @@ pre-commit = "2.20.0"
 fastavro = "1.6.1"
 coverage = { version = "^6.5.0", extras = ["toml"] }
 requests-mock = "1.10.0"
+typing-extensions = '4.4.0'
 
 [tool.poetry.scripts]
 pyiceberg = "pyiceberg.cli.console:run"
diff --git a/python/tests/expressions/test_expressions.py b/python/tests/expressions/test_expressions.py
index 6ccada73a4..2772ac2c34 100644
--- a/python/tests/expressions/test_expressions.py
+++ b/python/tests/expressions/test_expressions.py
@@ -14,17 +14,18 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+# pylint:disable=redefined-outer-name,eval-used
 
 import uuid
 from decimal import Decimal
 
 import pytest
+from typing_extensions import assert_type
 
 from pyiceberg.expressions import (
     AlwaysFalse,
     AlwaysTrue,
     And,
-    BooleanExpression,
     BoundEqualTo,
     BoundGreaterThan,
     BoundGreaterThanOrEqual,
@@ -33,15 +34,11 @@ from pyiceberg.expressions import (
     BoundIsNull,
     BoundLessThan,
     BoundLessThanOrEqual,
-    BoundLiteralPredicate,
     BoundNotEqualTo,
     BoundNotIn,
     BoundNotNaN,
     BoundNotNull,
-    BoundPredicate,
     BoundReference,
-    BoundSetPredicate,
-    BoundUnaryPredicate,
     EqualTo,
     GreaterThan,
     GreaterThanOrEqual,
@@ -50,7 +47,6 @@ from pyiceberg.expressions import (
     IsNull,
     LessThan,
     LessThanOrEqual,
-    LiteralPredicate,
     Not,
     NotEqualTo,
     NotIn,
@@ -58,11 +54,8 @@ from pyiceberg.expressions import (
     NotNull,
     Or,
     Reference,
-    SetPredicate,
-    UnaryPredicate,
-    UnboundPredicate,
 )
-from pyiceberg.expressions.literals import StringLiteral, literal
+from pyiceberg.expressions.literals import literal
 from pyiceberg.expressions.visitors import _from_byte_buffer
 from pyiceberg.schema import Accessor, Schema
 from pyiceberg.types import (
@@ -73,27 +66,10 @@ from pyiceberg.types import (
     NestedField,
     StringType,
 )
+from tests.conftest import FooStruct
 from tests.expressions.test_visitors import ExpressionA, ExpressionB
 
 
-@pytest.mark.parametrize(
-    "op, rep",
-    [
-        (
-            And(ExpressionA(), ExpressionB()),
-            "And(left=ExpressionA(), right=ExpressionB())",
-        ),
-        (
-            Or(ExpressionA(), ExpressionB()),
-            "Or(left=ExpressionA(), right=ExpressionB())",
-        ),
-        (Not(ExpressionA()), "Not(child=ExpressionA())"),
-    ],
-)
-def test_reprs(op: BooleanExpression, rep: str):
-    assert repr(op) == rep
-
-
 def test_isnull_inverse():
     assert ~IsNull(Reference("a")) == NotNull(Reference("a"))
 
@@ -202,48 +178,36 @@ def test_notnan_bind_nonfloat():
     assert NotNaN(Reference("i")).bind(schema) == AlwaysTrue()
 
 
-@pytest.mark.parametrize(
-    "op, string",
-    [
-        (And(ExpressionA(), ExpressionB()), "And(left=ExpressionA(), right=ExpressionB())"),
-        (Or(ExpressionA(), ExpressionB()), "Or(left=ExpressionA(), right=ExpressionB())"),
-        (Not(ExpressionA()), "Not(child=ExpressionA())"),
-    ],
-)
-def test_strs(op, string):
-    assert str(op) == string
-
-
 def test_ref_binding_case_sensitive(table_schema_simple: Schema):
-    ref = Reference[str]("foo")
+    ref = Reference("foo")
     bound = BoundReference[str](table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1))
     assert ref.bind(table_schema_simple, case_sensitive=True) == bound
 
 
 def test_ref_binding_case_sensitive_failure(table_schema_simple: Schema):
-    ref = Reference[str]("Foo")
+    ref = Reference("Foo")
     with pytest.raises(ValueError):
         ref.bind(table_schema_simple, case_sensitive=True)
 
 
 def test_ref_binding_case_insensitive(table_schema_simple: Schema):
-    ref = Reference[str]("Foo")
+    ref = Reference("Foo")
     bound = BoundReference[str](table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1))
     assert ref.bind(table_schema_simple, case_sensitive=False) == bound
 
 
 def test_ref_binding_case_insensitive_failure(table_schema_simple: Schema):
-    ref = Reference[str]("Foot")
+    ref = Reference("Foot")
     with pytest.raises(ValueError):
         ref.bind(table_schema_simple, case_sensitive=False)
 
 
 def test_in_to_eq():
-    assert In(Reference("x"), (literal(34.56),)) == EqualTo(Reference("x"), literal(34.56))
+    assert In("x", (34.56,)) == EqualTo(Reference("x"), 34.56)
 
 
 def test_empty_bind_in(table_schema_simple: Schema):
-    bound = BoundIn[str](BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), set())
+    bound = BoundIn(BoundReference[str](table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), set())
     assert bound == AlwaysFalse()
 
 
@@ -257,12 +221,12 @@ def test_bind_not_in_equal_term(table_schema_simple: Schema):
         BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), {literal("hello")}
     )
     assert (
-        BoundNotEqualTo[str](
+        BoundNotEqualTo(
             term=BoundReference(
                 field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
                 accessor=Accessor(position=0, inner=None),
             ),
-            literal=StringLiteral("hello"),
+            literal=literal("hello"),
         )
         == bound
     )
@@ -272,57 +236,69 @@ def test_in_empty():
     assert In(Reference("foo"), ()) == AlwaysFalse()
 
 
+def test_in_set():
+    assert In(Reference("foo"), {"a", "bc", "def"}).literals == {literal("a"), literal("bc"), literal("def")}
+
+
+def test_in_tuple():
+    assert In(Reference("foo"), ("a", "bc", "def")).literals == {literal("a"), literal("bc"), literal("def")}
+
+
+def test_in_list():
+    assert In(Reference("foo"), ["a", "bc", "def"]).literals == {literal("a"), literal("bc"), literal("def")}
+
+
 def test_not_in_empty():
     assert NotIn(Reference("foo"), ()) == AlwaysTrue()
 
 
 def test_not_in_equal():
-    assert NotIn(Reference("foo"), (literal("hello"),)) == NotEqualTo(term=Reference(name="foo"), literal=StringLiteral("hello"))
+    assert NotIn(Reference("foo"), ("hello",)) == NotEqualTo(term=Reference(name="foo"), literal="hello")
 
 
 def test_bind_in(table_schema_simple: Schema):
-    bound = BoundIn(
+    bound = BoundIn[str](
         BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)),
         {literal("hello"), literal("world")},
     )
-    assert In(Reference("foo"), (literal("hello"), literal("world"))).bind(table_schema_simple) == bound
+    assert In(Reference("foo"), ("hello", "world")).bind(table_schema_simple) == bound
 
 
 def test_bind_in_invert(table_schema_simple: Schema):
-    bound = BoundIn(
+    bound = BoundIn[str](
         BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)),
         {literal("hello"), literal("world")},
     )
-    assert ~bound == BoundNotIn(
+    assert ~bound == BoundNotIn[str](
         BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)),
         {literal("hello"), literal("world")},
     )
 
 
 def test_bind_not_in_invert(table_schema_simple: Schema):
-    bound = BoundNotIn(
+    bound = BoundNotIn[str](
         BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)),
         {literal("hello"), literal("world")},
     )
-    assert ~bound == BoundIn(
+    assert ~bound == BoundIn[str](
         BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)),
         {literal("hello"), literal("world")},
     )
 
 
 def test_bind_dedup(table_schema_simple: Schema):
-    bound = BoundIn(
+    bound = BoundIn[str](
         BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)),
         {literal("hello"), literal("world")},
     )
-    assert In(Reference("foo"), (literal("hello"), literal("world"), literal("world"))).bind(table_schema_simple) == bound
+    assert In(Reference("foo"), ("hello", "world", "world")).bind(table_schema_simple) == bound
 
 
 def test_bind_dedup_to_eq(table_schema_simple: Schema):
     bound = BoundEqualTo(
         BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), literal("hello")
     )
-    assert In(Reference("foo"), (literal("hello"), literal("hello"))).bind(table_schema_simple) == bound
+    assert In(Reference("foo"), ("hello", "hello")).bind(table_schema_simple) == bound
 
 
 def test_bound_equal_to_invert(table_schema_simple: Schema):
@@ -330,11 +306,11 @@ def test_bound_equal_to_invert(table_schema_simple: Schema):
         BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), literal("hello")
     )
     assert ~bound == BoundNotEqualTo(
-        term=BoundReference[str](
+        term=BoundReference(
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
         ),
-        literal=StringLiteral("hello"),
+        literal=literal("hello"),
     )
 
 
@@ -343,11 +319,11 @@ def test_bound_not_equal_to_invert(table_schema_simple: Schema):
         BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), literal("hello")
     )
     assert ~bound == BoundEqualTo(
-        term=BoundReference[str](
+        term=BoundReference(
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
         ),
-        literal=StringLiteral("hello"),
+        literal=literal("hello"),
     )
 
 
@@ -356,11 +332,11 @@ def test_bound_greater_than_or_equal_invert(table_schema_simple: Schema):
         BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), literal("hello")
     )
     assert ~bound == BoundLessThan(
-        term=BoundReference[str](
+        term=BoundReference(
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
         ),
-        literal=StringLiteral("hello"),
+        literal=literal("hello"),
     )
 
 
@@ -369,11 +345,11 @@ def test_bound_greater_than_invert(table_schema_simple: Schema):
         BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), literal("hello")
     )
     assert ~bound == BoundLessThanOrEqual(
-        term=BoundReference[str](
+        term=BoundReference(
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
         ),
-        literal=StringLiteral("hello"),
+        literal=literal("hello"),
     )
 
 
@@ -382,11 +358,11 @@ def test_bound_less_than_invert(table_schema_simple: Schema):
         BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), literal("hello")
     )
     assert ~bound == BoundGreaterThanOrEqual(
-        term=BoundReference[str](
+        term=BoundReference(
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
         ),
-        literal=StringLiteral("hello"),
+        literal=literal("hello"),
     )
 
 
@@ -395,11 +371,11 @@ def test_bound_less_than_or_equal_invert(table_schema_simple: Schema):
         BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), literal("hello")
     )
     assert ~bound == BoundGreaterThan(
-        term=BoundReference[str](
+        term=BoundReference(
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
         ),
-        literal=StringLiteral("hello"),
+        literal=literal("hello"),
     )
 
 
@@ -409,14 +385,14 @@ def test_not_equal_to_invert():
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
         ),
-        literal=StringLiteral("hello"),
+        literal="hello",
     )
     assert ~bound == EqualTo(
         term=BoundReference(
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
         ),
-        literal=StringLiteral("hello"),
+        literal="hello",
     )
 
 
@@ -426,14 +402,14 @@ def test_greater_than_or_equal_invert():
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
         ),
-        literal=StringLiteral("hello"),
+        literal="hello",
     )
     assert ~bound == LessThan(
         term=BoundReference(
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
         ),
-        literal=StringLiteral("hello"),
+        literal="hello",
     )
 
 
@@ -443,27 +419,27 @@ def test_less_than_or_equal_invert():
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
         ),
-        literal=StringLiteral("hello"),
+        literal="hello",
     )
     assert ~bound == GreaterThan(
         term=BoundReference(
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
         ),
-        literal=StringLiteral("hello"),
+        literal="hello",
     )
 
 
 @pytest.mark.parametrize(
     "pred",
     [
-        NotIn(Reference("foo"), (literal("hello"), literal("world"))),
-        NotEqualTo(Reference("foo"), literal("hello")),
-        EqualTo(Reference("foo"), literal("hello")),
-        GreaterThan(Reference("foo"), literal("hello")),
-        LessThan(Reference("foo"), literal("hello")),
-        GreaterThanOrEqual(Reference("foo"), literal("hello")),
-        LessThanOrEqual(Reference("foo"), literal("hello")),
+        NotIn(Reference("foo"), ("hello", "world")),
+        NotEqualTo(Reference("foo"), "hello"),
+        EqualTo(Reference("foo"), "hello"),
+        GreaterThan(Reference("foo"), "hello"),
+        LessThan(Reference("foo"), "hello"),
+        GreaterThanOrEqual(Reference("foo"), "hello"),
+        LessThanOrEqual(Reference("foo"), "hello"),
     ],
 )
 def test_bind(pred, table_schema_simple: Schema):
@@ -475,14 +451,14 @@ def test_bind(pred, table_schema_simple: Schema):
 @pytest.mark.parametrize(
     "pred",
     [
-        In(Reference("Bar"), (literal(5), literal(2))),
-        NotIn(Reference("Bar"), (literal(5), literal(2))),
-        NotEqualTo(Reference("Bar"), literal(5)),
-        EqualTo(Reference("Bar"), literal(5)),
-        GreaterThan(Reference("Bar"), literal(5)),
-        LessThan(Reference("Bar"), literal(5)),
-        GreaterThanOrEqual(Reference("Bar"), literal(5)),
-        LessThanOrEqual(Reference("Bar"), literal(5)),
+        In(Reference("Bar"), (5, 2)),
+        NotIn(Reference("Bar"), (5, 2)),
+        NotEqualTo(Reference("Bar"), 5),
+        EqualTo(Reference("Bar"), 5),
+        GreaterThan(Reference("Bar"), 5),
+        LessThan(Reference("Bar"), 5),
+        GreaterThanOrEqual(Reference("Bar"), 5),
+        LessThanOrEqual(Reference("Bar"), 5),
     ],
 )
 def test_bind_case_insensitive(pred, table_schema_simple: Schema):
@@ -508,14 +484,14 @@ def test_bind_case_insensitive(pred, table_schema_simple: Schema):
         (ExpressionA(), ExpressionA(), ExpressionB()),
         (ExpressionB(), ExpressionB(), ExpressionA()),
         (
-            In(Reference("foo"), (literal("hello"), literal("world"))),
-            In(Reference("foo"), (literal("hello"), literal("world"))),
-            In(Reference("not_foo"), (literal("hello"), literal("world"))),
+            In(Reference("foo"), ("hello", "world")),
+            In(Reference("foo"), ("hello", "world")),
+            In(Reference("not_foo"), ("hello", "world")),
         ),
         (
-            In(Reference("foo"), (literal("hello"), literal("world"))),
-            In(Reference("foo"), (literal("hello"), literal("world"))),
-            In(Reference("foo"), (literal("goodbye"), literal("world"))),
+            In(Reference("foo"), ("hello", "world")),
+            In(Reference("foo"), ("hello", "world")),
+            In(Reference("foo"), ("goodbye", "world")),
         ),
     ],
 )
@@ -539,16 +515,16 @@ def test_eq(exp, testexpra, testexprb):
             ExpressionA(),
         ),
         (
-            In(Reference("foo"), (literal("hello"), literal("world"))),
-            NotIn(Reference("foo"), (literal("hello"), literal("world"))),
+            In(Reference("foo"), ("hello", "world")),
+            NotIn(Reference("foo"), ("hello", "world")),
         ),
         (
-            NotIn(Reference("foo"), (literal("hello"), literal("world"))),
-            In(Reference("foo"), (literal("hello"), literal("world"))),
+            NotIn(Reference("foo"), ("hello", "world")),
+            In(Reference("foo"), ("hello", "world")),
         ),
-        (GreaterThan(Reference("foo"), literal(5)), LessThanOrEqual(Reference("foo"), literal(5))),
-        (LessThan(Reference("foo"), literal(5)), GreaterThanOrEqual(Reference("foo"), literal(5))),
-        (EqualTo(Reference("foo"), literal(5)), NotEqualTo(Reference("foo"), literal(5))),
+        (GreaterThan(Reference("foo"), 5), LessThanOrEqual(Reference("foo"), 5)),
+        (LessThan(Reference("foo"), 5), GreaterThanOrEqual(Reference("foo"), 5)),
+        (EqualTo(Reference("foo"), 5), NotEqualTo(Reference("foo"), 5)),
         (
             ExpressionA(),
             ExpressionB(),
@@ -632,24 +608,271 @@ def test_accessor_base_class(foo_struct):
     assert Accessor(position=11).get(foo_struct) == b"\x19\x04\x9e?"
 
 
-def test_bound_reference_str_and_repr():
-    """Test str and repr of BoundReference"""
-    field = NestedField(field_id=1, name="foo", field_type=StringType(), required=False)
-    position1_accessor = Accessor(position=1)
-    bound_ref = BoundReference(field=field, accessor=position1_accessor)
-    assert str(bound_ref) == f"BoundReference(field={repr(field)}, accessor={repr(position1_accessor)})"
-    assert repr(bound_ref) == f"BoundReference(field={repr(field)}, accessor={repr(position1_accessor)})"
+@pytest.fixture
+def field() -> NestedField:
+    return NestedField(field_id=1, name="foo", field_type=StringType(), required=False)
+
+
+@pytest.fixture
+def accessor() -> Accessor:
+    return Accessor(position=1)
+
+
+@pytest.fixture
+def term(field: NestedField, accessor: Accessor) -> BoundReference:
+    return BoundReference(
+        field=field,
+        accessor=accessor,
+    )
+
+
+def test_bound_reference(field: NestedField, accessor: Accessor) -> None:
+    bound_ref = BoundReference(field=field, accessor=accessor)
+    assert str(bound_ref) == f"BoundReference(field={repr(field)}, accessor={repr(accessor)})"
+    assert repr(bound_ref) == f"BoundReference(field={repr(field)}, accessor={repr(accessor)})"
+    assert bound_ref == eval(repr(bound_ref))
+
+
+def test_reference() -> None:
+    abc = "abc"
+    ref = Reference(abc)
+    assert str(ref) == "Reference(name='abc')"
+    assert repr(ref) == "Reference(name='abc')"
+    assert ref == eval(repr(ref))
+
+
+def test_and() -> None:
+    null = IsNull(Reference("a"))
+    nan = IsNaN(Reference("b"))
+    and_ = And(null, nan)
+    assert str(and_) == f"And(left={str(null)}, right={str(nan)})"
+    assert repr(and_) == f"And(left={repr(null)}, right={repr(nan)})"
+    assert and_ == eval(repr(and_))
+
+
+def test_or() -> None:
+    null = IsNull(Reference("a"))
+    nan = IsNaN(Reference("b"))
+    or_ = Or(null, nan)
+    assert str(or_) == f"Or(left={str(null)}, right={str(nan)})"
+    assert repr(or_) == f"Or(left={repr(null)}, right={repr(nan)})"
+    assert or_ == eval(repr(or_))
+
+
+def test_not() -> None:
+    null = IsNull(Reference("a"))
+    or_ = Not(null)
+    assert str(or_) == f"Not(child={str(null)})"
+    assert repr(or_) == f"Not(child={repr(null)})"
+    assert or_ == eval(repr(or_))
+
+
+def test_always_true() -> None:
+    always_true = AlwaysTrue()
+    assert str(always_true) == "AlwaysTrue()"
+    assert repr(always_true) == "AlwaysTrue()"
+    assert always_true == eval(repr(always_true))
+
+
+def test_always_false() -> None:
+    always_false = AlwaysFalse()
+    assert str(always_false) == "AlwaysFalse()"
+    assert repr(always_false) == "AlwaysFalse()"
+    assert always_false == eval(repr(always_false))
 
 
 def test_bound_reference_field_property():
-    """Test str and repr of BoundReference"""
     field = NestedField(field_id=1, name="foo", field_type=StringType(), required=False)
     position1_accessor = Accessor(position=1)
     bound_ref = BoundReference(field=field, accessor=position1_accessor)
     assert bound_ref.field == NestedField(field_id=1, name="foo", field_type=StringType(), required=False)
 
 
-def test_bound_reference(table_schema_simple, foo_struct):
+def test_bound_is_null(term: BoundReference) -> None:
+    bound_is_null = BoundIsNull(term)
+    assert str(bound_is_null) == f"BoundIsNull(term={str(term)})"
+    assert repr(bound_is_null) == f"BoundIsNull(term={repr(term)})"
+    assert bound_is_null == eval(repr(bound_is_null))
+
+
+def test_bound_is_not_null(term: BoundReference) -> None:
+    bound_not_null = BoundNotNull(term)
+    assert str(bound_not_null) == f"BoundNotNull(term={str(term)})"
+    assert repr(bound_not_null) == f"BoundNotNull(term={repr(term)})"
+    assert bound_not_null == eval(repr(bound_not_null))
+
+
+def test_is_null() -> None:
+    ref = Reference("a")
+    is_null = IsNull(ref)
+    assert str(is_null) == f"IsNull(term={str(ref)})"
+    assert repr(is_null) == f"IsNull(term={repr(ref)})"
+    assert is_null == eval(repr(is_null))
+
+
+def test_not_null() -> None:
+    ref = Reference("a")
+    non_null = NotNull(ref)
+    assert str(non_null) == f"NotNull(term={str(ref)})"
+    assert repr(non_null) == f"NotNull(term={repr(ref)})"
+    assert non_null == eval(repr(non_null))
+
+
+def test_bound_is_nan(accessor: Accessor) -> None:
+    # We need a FloatType here
+    term = BoundReference[float](
+        field=NestedField(field_id=1, name="foo", field_type=FloatType(), required=False),
+        accessor=accessor,
+    )
+    bound_is_nan = BoundIsNaN(term)
+    assert str(bound_is_nan) == f"BoundIsNaN(term={str(term)})"
+    assert repr(bound_is_nan) == f"BoundIsNaN(term={repr(term)})"
+    assert bound_is_nan == eval(repr(bound_is_nan))
+
+
+def test_bound_is_not_nan(accessor: Accessor) -> None:
+    # We need a FloatType here
+    term = BoundReference[float](
+        field=NestedField(field_id=1, name="foo", field_type=FloatType(), required=False),
+        accessor=accessor,
+    )
+    bound_not_nan = BoundNotNaN(term)
+    assert str(bound_not_nan) == f"BoundNotNaN(term={str(term)})"
+    assert repr(bound_not_nan) == f"BoundNotNaN(term={repr(term)})"
+    assert bound_not_nan == eval(repr(bound_not_nan))
+
+
+def test_is_nan() -> None:
+    ref = Reference("a")
+    is_nan = IsNaN(ref)
+    assert str(is_nan) == f"IsNaN(term={str(ref)})"
+    assert repr(is_nan) == f"IsNaN(term={repr(ref)})"
+    assert is_nan == eval(repr(is_nan))
+
+
+def test_not_nan() -> None:
+    ref = Reference("a")
+    not_nan = NotNaN(ref)
+    assert str(not_nan) == f"NotNaN(term={str(ref)})"
+    assert repr(not_nan) == f"NotNaN(term={repr(ref)})"
+    assert not_nan == eval(repr(not_nan))
+
+
+def test_bound_in(term: BoundReference) -> None:
+    bound_in = BoundIn(term, {literal("a"), literal("b"), literal("c")})
+    assert str(bound_in) == f"BoundIn({str(term)}, {{a, b, c}})"
+    assert repr(bound_in) == f"BoundIn({repr(term)}, {{literal('a'), literal('b'), literal('c')}})"
+    assert bound_in == eval(repr(bound_in))
+
+
+def test_bound_not_in(term: BoundReference) -> None:
+    bound_not_in = BoundNotIn(term, {literal("a"), literal("b"), literal("c")})
+    assert str(bound_not_in) == f"BoundNotIn({str(term)}, {{a, b, c}})"
+    assert repr(bound_not_in) == f"BoundNotIn({repr(term)}, {{literal('a'), literal('b'), literal('c')}})"
+    assert bound_not_in == eval(repr(bound_not_in))
+
+
+def test_in() -> None:
+    ref = Reference("a")
+    unbound_in = In(ref, {"a", "b", "c"})
+    assert str(unbound_in) == f"In({str(ref)}, {{a, b, c}})"
+    assert repr(unbound_in) == f"In({repr(ref)}, {{literal('a'), literal('b'), literal('c')}})"
+    assert unbound_in == eval(repr(unbound_in))
+
+
+def test_not_in() -> None:
+    ref = Reference("a")
+    not_in = NotIn(ref, {"a", "b", "c"})
+    assert str(not_in) == f"NotIn({str(ref)}, {{a, b, c}})"
+    assert repr(not_in) == f"NotIn({repr(ref)}, {{literal('a'), literal('b'), literal('c')}})"
+    assert not_in == eval(repr(not_in))
+
+
+def test_bound_equal_to(term: BoundReference) -> None:
+    bound_equal_to = BoundEqualTo(term, literal("a"))
+    assert str(bound_equal_to) == f"BoundEqualTo(term={str(term)}, literal=literal('a'))"
+    assert repr(bound_equal_to) == f"BoundEqualTo(term={repr(term)}, literal=literal('a'))"
+    assert bound_equal_to == eval(repr(bound_equal_to))
+
+
+def test_bound_not_equal_to(term: BoundReference) -> None:
+    bound_not_equal_to = BoundNotEqualTo(term, literal("a"))
+    assert str(bound_not_equal_to) == f"BoundNotEqualTo(term={str(term)}, literal=literal('a'))"
+    assert repr(bound_not_equal_to) == f"BoundNotEqualTo(term={repr(term)}, literal=literal('a'))"
+    assert bound_not_equal_to == eval(repr(bound_not_equal_to))
+
+
+def test_bound_greater_than_or_equal_to(term: BoundReference) -> None:
+    bound_greater_than_or_equal_to = BoundGreaterThanOrEqual(term, literal("a"))
+    assert str(bound_greater_than_or_equal_to) == f"BoundGreaterThanOrEqual(term={str(term)}, literal=literal('a'))"
+    assert repr(bound_greater_than_or_equal_to) == f"BoundGreaterThanOrEqual(term={repr(term)}, literal=literal('a'))"
+    assert bound_greater_than_or_equal_to == eval(repr(bound_greater_than_or_equal_to))
+
+
+def test_bound_greater_than(term: BoundReference) -> None:
+    bound_greater_than = BoundGreaterThan(term, literal("a"))
+    assert str(bound_greater_than) == f"BoundGreaterThan(term={str(term)}, literal=literal('a'))"
+    assert repr(bound_greater_than) == f"BoundGreaterThan(term={repr(term)}, literal=literal('a'))"
+    assert bound_greater_than == eval(repr(bound_greater_than))
+
+
+def test_bound_less_than(term: BoundReference) -> None:
+    bound_less_than = BoundLessThan(term, literal("a"))
+    assert str(bound_less_than) == f"BoundLessThan(term={str(term)}, literal=literal('a'))"
+    assert repr(bound_less_than) == f"BoundLessThan(term={repr(term)}, literal=literal('a'))"
+    assert bound_less_than == eval(repr(bound_less_than))
+
+
+def test_bound_less_than_or_equal(term: BoundReference) -> None:
+    bound_less_than_or_equal = BoundLessThanOrEqual(term, literal("a"))
+    assert str(bound_less_than_or_equal) == f"BoundLessThanOrEqual(term={str(term)}, literal=literal('a'))"
+    assert repr(bound_less_than_or_equal) == f"BoundLessThanOrEqual(term={repr(term)}, literal=literal('a'))"
+    assert bound_less_than_or_equal == eval(repr(bound_less_than_or_equal))
+
+
+def test_equal_to() -> None:
+    equal_to = EqualTo(Reference("a"), literal("a"))
+    assert str(equal_to) == "EqualTo(term=Reference(name='a'), literal=literal('a'))"
+    assert repr(equal_to) == "EqualTo(term=Reference(name='a'), literal=literal('a'))"
+    assert equal_to == eval(repr(equal_to))
+
+
+def test_not_equal_to() -> None:
+    not_equal_to = NotEqualTo(Reference("a"), literal("a"))
+    assert str(not_equal_to) == "NotEqualTo(term=Reference(name='a'), literal=literal('a'))"
+    assert repr(not_equal_to) == "NotEqualTo(term=Reference(name='a'), literal=literal('a'))"
+    assert not_equal_to == eval(repr(not_equal_to))
+
+
+def test_greater_than_or_equal_to() -> None:
+    greater_than_or_equal_to = GreaterThanOrEqual(Reference("a"), literal("a"))
+    assert str(greater_than_or_equal_to) == "GreaterThanOrEqual(term=Reference(name='a'), literal=literal('a'))"
+    assert repr(greater_than_or_equal_to) == "GreaterThanOrEqual(term=Reference(name='a'), literal=literal('a'))"
+    assert greater_than_or_equal_to == eval(repr(greater_than_or_equal_to))
+
+
+def test_greater_than() -> None:
+    greater_than = GreaterThan(Reference("a"), literal("a"))
+    assert str(greater_than) == "GreaterThan(term=Reference(name='a'), literal=literal('a'))"
+    assert repr(greater_than) == "GreaterThan(term=Reference(name='a'), literal=literal('a'))"
+    assert greater_than == eval(repr(greater_than))
+
+
+def test_less_than() -> None:
+    less_than = LessThan(Reference("a"), literal("a"))
+    assert str(less_than) == "LessThan(term=Reference(name='a'), literal=literal('a'))"
+    assert repr(less_than) == "LessThan(term=Reference(name='a'), literal=literal('a'))"
+    assert less_than == eval(repr(less_than))
+
+
+def test_less_than_or_equal() -> None:
+    less_than_or_equal = LessThanOrEqual(Reference("a"), literal("a"))
+    assert str(less_than_or_equal) == "LessThanOrEqual(term=Reference(name='a'), literal=literal('a'))"
+    assert repr(less_than_or_equal) == "LessThanOrEqual(term=Reference(name='a'), literal=literal('a'))"
+    assert less_than_or_equal == eval(repr(less_than_or_equal))
+
+
+def test_bound_reference_eval(table_schema_simple: Schema, foo_struct: FooStruct) -> None:
     """Test creating a BoundReference and evaluating it on a StructProtocol"""
     foo_struct.set(pos=1, value="foovalue")
     foo_struct.set(pos=2, value=123)
@@ -672,48 +895,6 @@ def test_bound_reference(table_schema_simple, foo_struct):
     assert bound_ref3.eval(foo_struct) is True
 
 
-def test_bound_predicate_invert():
-    with pytest.raises(NotImplementedError):
-        _ = ~BoundPredicate(
-            term=BoundReference(
-                field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
-                accessor=Accessor(position=0, inner=None),
-            )
-        )
-
-
-def test_bound_unary_predicate_invert():
-    with pytest.raises(NotImplementedError):
-        _ = ~BoundUnaryPredicate(
-            term=BoundReference(
-                field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
-                accessor=Accessor(position=0, inner=None),
-            )
-        )
-
-
-def test_bound_set_predicate_invert():
-    with pytest.raises(NotImplementedError):
-        _ = ~BoundSetPredicate(
-            term=BoundReference(
-                field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
-                accessor=Accessor(position=0, inner=None),
-            ),
-            literals={literal("hello"), literal("world")},
-        )
-
-
-def test_bound_literal_predicate_invert():
-    with pytest.raises(NotImplementedError):
-        _ = ~BoundLiteralPredicate(
-            term=BoundReference(
-                field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
-                accessor=Accessor(position=0, inner=None),
-            ),
-            literal=literal("world"),
-        )
-
-
 def test_non_primitive_from_byte_buffer():
     with pytest.raises(ValueError) as exc_info:
         _ = _from_byte_buffer(ListType(element_id=1, element_type=StringType()), b"\0x00")
@@ -721,26 +902,25 @@ def test_non_primitive_from_byte_buffer():
     assert str(exc_info.value) == "Expected a PrimitiveType, got: <class 'pyiceberg.types.ListType'>"
 
 
-def test_unbound_predicate_invert():
-    with pytest.raises(NotImplementedError):
-        _ = ~UnboundPredicate(term=Reference("a"))
-
+def test_string_argument_unbound_unary():
+    assert IsNull("a") == IsNull(Reference("a"))
 
-def test_unbound_predicate_bind(table_schema_simple: Schema):
-    with pytest.raises(NotImplementedError):
-        _ = UnboundPredicate(term=Reference("a")).bind(table_schema_simple)
 
+def test_string_argument_unbound_literal():
+    assert EqualTo("a", "b") == EqualTo(Reference("a"), "b")
 
-def test_unbound_unary_predicate_invert():
-    with pytest.raises(NotImplementedError):
-        _ = ~UnaryPredicate(term=Reference("a"))
 
+def test_string_argument_unbound_set():
+    assert In("a", {"b", "c"}) == In(Reference("a"), {"b", "c"})
 
-def test_unbound_set_predicate_invert():
-    with pytest.raises(NotImplementedError):
-        _ = ~SetPredicate(term=Reference("a"), literals=(literal("hello"), literal("world")))
 
+#   __  __      ___
+#  |  \/  |_  _| _ \_  _
+#  | |\/| | || |  _/ || |
+#  |_|  |_|\_, |_|  \_, |
+#          |__/     |__/
 
-def test_unbound_literal_predicate_invert():
-    with pytest.raises(NotImplementedError):
-        _ = ~LiteralPredicate(term=Reference("a"), literal=literal("hello"))
+assert_type(EqualTo("a", "b"), EqualTo[str])
+assert_type(In("a", ("a", "b", "c")), In[str])
+assert_type(In("a", (1, 2, 3)), In[int])
+assert_type(NotIn("a", ("a", "b", "c")), NotIn[str])
diff --git a/python/tests/expressions/test_literals.py b/python/tests/expressions/test_literals.py
index 96656ba825..d203195930 100644
--- a/python/tests/expressions/test_literals.py
+++ b/python/tests/expressions/test_literals.py
@@ -19,8 +19,10 @@
 import datetime
 import uuid
 from decimal import Decimal
+from typing import Set
 
 import pytest
+from typing_extensions import assert_type
 
 from pyiceberg.expressions.literals import (
     BinaryLiteral,
@@ -34,6 +36,7 @@ from pyiceberg.expressions.literals import (
     FloatLiteral,
     IntAboveMax,
     IntBelowMin,
+    Literal,
     LongLiteral,
     StringLiteral,
     TimeLiteral,
@@ -367,13 +370,11 @@ def test_string_to_decimal_literal():
 
 def test_python_date_conversion():
     one_day_str = "2022-03-28"
-    one_day_date = datetime.date(2022, 3, 28)
 
     from_str_lit = literal(one_day_str).to(DateType())
-    from_date_lit = literal(one_day_date)
 
-    assert isinstance(from_date_lit, DateLiteral)
-    assert from_str_lit == from_date_lit
+    assert isinstance(from_str_lit, DateLiteral)
+    assert from_str_lit.value == 19079
 
 
 @pytest.mark.parametrize(
@@ -385,14 +386,12 @@ def test_python_date_conversion():
         (literal(34.11), FloatType()),
         (literal(3.5028235e38), DoubleType()),
         (literal(Decimal(34.55).quantize(Decimal("0.01"))), DecimalType(9, 2)),
-        (literal(datetime.date(2017, 8, 18)), DateType()),
         (literal("2017-08-18"), DateType()),
         (literal("14:21:01.919"), TimeType()),
         (literal("2017-08-18T14:21:01.919"), TimestampType()),
         (literal("abc"), StringType()),
         (literal(uuid.uuid4()), UUIDType()),
         (literal(bytes([0x01, 0x02, 0x03])), FixedType(3)),
-        (literal(bytearray([0x03, 0x04, 0x05, 0x06])), BinaryType()),
     ],
 )
 def test_identity_conversions(lit, primitive_type):
@@ -412,8 +411,8 @@ def test_fixed_literal():
 
 
 def test_binary_literal():
-    bin_lit012 = literal(bytearray([0x00, 0x01, 0x02]))
-    bin_lit013 = literal(bytearray([0x00, 0x01, 0x03]))
+    bin_lit012 = literal(bytes([0x00, 0x01, 0x02]))
+    bin_lit013 = literal(bytes([0x00, 0x01, 0x03]))
     assert bin_lit012 == bin_lit012
     assert bin_lit012 != bin_lit013
     assert bin_lit012 < bin_lit013
@@ -424,7 +423,7 @@ def test_binary_literal():
 
 
 def test_raise_on_comparison_to_none():
-    bin_lit012 = literal(bytearray([0x00, 0x01, 0x02]))
+    bin_lit012 = literal(bytes([0x00, 0x01, 0x02]))
     fixed_lit012 = literal(bytes([0x00, 0x01, 0x02]))
 
     with pytest.raises(AttributeError):
@@ -453,7 +452,7 @@ def test_raise_on_comparison_to_none():
 
 
 def test_binary_to_fixed():
-    lit = literal(bytearray([0x00, 0x01, 0x02]))
+    lit = literal(bytes([0x00, 0x01, 0x02]))
     fixed_lit = lit.to(FixedType(3))
     assert fixed_lit is not None
     assert lit.value == fixed_lit.value
@@ -464,7 +463,7 @@ def test_binary_to_fixed():
 
 
 def test_binary_to_smaller_fixed_none():
-    lit = literal(bytearray([0x00, 0x01, 0x02]))
+    lit = literal(bytes([0x00, 0x01, 0x02]))
 
     with pytest.raises(TypeError) as e:
         _ = lit.to(FixedType(2))
@@ -479,7 +478,7 @@ def test_fixed_to_binary():
 
 
 def test_fixed_to_smaller_fixed_none():
-    lit = literal(bytearray([0x00, 0x01, 0x02])).to(FixedType(3))
+    lit = literal(bytes([0x00, 0x01, 0x02])).to(FixedType(3))
     with pytest.raises(ValueError) as e:
         lit.to(lit.to(FixedType(2)))
     assert "Could not convert b'\\x00\\x01\\x02' into a fixed[2]" in str(e.value)
@@ -734,7 +733,7 @@ def test_invalid_fixed_conversions():
 
 def test_invalid_binary_conversions():
     assert_invalid_conversions(
-        literal(bytearray([0x00, 0x01, 0x02])),
+        literal(bytes([0x00, 0x01, 0x02])),
         [
             BooleanType(),
             IntegerType(),
@@ -804,3 +803,19 @@ def test_string_to_decimal_type_invalid_value():
     with pytest.raises(ValueError) as e:
         _ = literal("18.15").to(DecimalType(10, 0))
     assert "Could not convert 18.15 into a decimal(10, 0), scales differ 0 <> 2" in str(e.value)
+
+
+#   __  __      ___
+#  |  \/  |_  _| _ \_  _
+#  | |\/| | || |  _/ || |
+#  |_|  |_|\_, |_|  \_, |
+#          |__/     |__/
+
+assert_type(literal("str"), Literal[str])
+assert_type(literal(True), Literal[bool])
+assert_type(literal(123), Literal[int])
+assert_type(literal(123.4), Literal[float])
+assert_type(literal(uuid.UUID("f79c3e09-677c-4bbd-a479-3f349cb785e7")), Literal[uuid.UUID])
+assert_type(literal(bytes([0x01, 0x02, 0x03])), Literal[bytes])
+assert_type(literal(Decimal("19.25")), Literal[Decimal])
+assert_type({literal(1), literal(2), literal(3)}, Set[Literal[int]])
diff --git a/python/tests/expressions/test_visitors.py b/python/tests/expressions/test_visitors.py
index fd91e8a219..62407013fc 100644
--- a/python/tests/expressions/test_visitors.py
+++ b/python/tests/expressions/test_visitors.py
@@ -34,16 +34,12 @@ from pyiceberg.expressions import (
     BoundIsNull,
     BoundLessThan,
     BoundLessThanOrEqual,
-    BoundLiteralPredicate,
     BoundNotEqualTo,
     BoundNotIn,
     BoundNotNaN,
     BoundNotNull,
-    BoundPredicate,
     BoundReference,
-    BoundSetPredicate,
     BoundTerm,
-    BoundUnaryPredicate,
     EqualTo,
     GreaterThan,
     GreaterThanOrEqual,
@@ -52,7 +48,6 @@ from pyiceberg.expressions import (
     IsNull,
     LessThan,
     LessThanOrEqual,
-    LiteralPredicate,
     Not,
     NotEqualTo,
     NotIn,
@@ -60,16 +55,8 @@ from pyiceberg.expressions import (
     NotNull,
     Or,
     Reference,
-    SetPredicate,
-    UnaryPredicate,
-    UnboundPredicate,
-)
-from pyiceberg.expressions.literals import (
-    Literal,
-    LongLiteral,
-    StringLiteral,
-    literal,
 )
+from pyiceberg.expressions.literals import Literal, literal
 from pyiceberg.expressions.visitors import (
     BindVisitor,
     BooleanExpressionVisitor,
@@ -289,13 +276,14 @@ def test_boolean_expression_visit_raise_not_implemented_error():
 
 
 def test_bind_visitor_already_bound(table_schema_simple: Schema):
-    bound = BoundEqualTo(
-        BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)), literal("hello")
+    bound = BoundEqualTo[str](
+        term=BoundReference(table_schema_simple.find_field(1), table_schema_simple.accessor_for_field(1)),
+        literal=literal("hello"),
     )
     with pytest.raises(TypeError) as exc_info:
-        BindVisitor(visit(bound, visitor=BindVisitor(schema=table_schema_simple)))  # type: ignore
+        visit(bound, visitor=BindVisitor(schema=table_schema_simple))
     assert (
-        "Found already bound predicate: BoundEqualTo(term=BoundReference(field=NestedField(field_id=1, name='foo', field_type=StringType(), required=False), accessor=Accessor(position=0,inner=None)), literal=StringLiteral(hello))"
+        "Found already bound predicate: BoundEqualTo(term=BoundReference(field=NestedField(field_id=1, name='foo', field_type=StringType(), required=False), accessor=Accessor(position=0,inner=None)), literal=literal('hello'))"
         == str(exc_info.value)
     )
 
@@ -339,61 +327,61 @@ def test_always_false_or_always_true_expression_binding(table_schema_simple: Sch
     [
         (
             And(
-                In(Reference("foo"), (literal("foo"), literal("bar"))),
-                In(Reference("bar"), (literal(1), literal(2), literal(3))),
+                In(Reference("foo"), {"foo", "bar"}),
+                In(Reference("bar"), {1, 2, 3}),
             ),
             And(
-                BoundIn[str](
+                BoundIn(
                     BoundReference(
                         field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
                         accessor=Accessor(position=0, inner=None),
                     ),
-                    {StringLiteral("foo"), StringLiteral("bar")},
+                    {literal("foo"), literal("bar")},
                 ),
                 BoundIn[int](
                     BoundReference(
                         field=NestedField(field_id=2, name="bar", field_type=IntegerType(), required=True),
                         accessor=Accessor(position=1, inner=None),
                     ),
-                    {LongLiteral(1), LongLiteral(2), LongLiteral(3)},
+                    {literal(1), literal(2), literal(3)},
                 ),
             ),
         ),
         (
             And(
-                In(Reference("foo"), (literal("bar"), literal("baz"))),
+                In(Reference("foo"), ("bar", "baz")),
                 In(
                     Reference("bar"),
-                    (literal(1),),
+                    (1,),
                 ),
                 In(
                     Reference("foo"),
-                    (literal("baz"),),
+                    ("baz",),
                 ),
             ),
             And(
                 And(
-                    BoundIn[str](
+                    BoundIn(
                         BoundReference(
                             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
                             accessor=Accessor(position=0, inner=None),
                         ),
-                        {StringLiteral("bar"), StringLiteral("baz")},
+                        {literal("bar"), literal("baz")},
                     ),
                     BoundEqualTo[int](
                         BoundReference(
                             field=NestedField(field_id=2, name="bar", field_type=IntegerType(), required=True),
                             accessor=Accessor(position=1, inner=None),
                         ),
-                        LongLiteral(1),
+                        literal(1),
                     ),
                 ),
-                BoundEqualTo[str](
+                BoundEqualTo(
                     BoundReference(
                         field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
                         accessor=Accessor(position=0, inner=None),
                     ),
-                    StringLiteral("baz"),
+                    literal("baz"),
                 ),
             ),
         ),
@@ -410,61 +398,61 @@ def test_and_expression_binding(unbound_and_expression, expected_bound_expressio
     [
         (
             Or(
-                In(Reference("foo"), (literal("foo"), literal("bar"))),
-                In(Reference("bar"), (literal(1), literal(2), literal(3))),
+                In(Reference("foo"), ("foo", "bar")),
+                In(Reference("bar"), (1, 2, 3)),
             ),
             Or(
-                BoundIn[str](
+                BoundIn(
                     BoundReference(
                         field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
                         accessor=Accessor(position=0, inner=None),
                     ),
-                    {StringLiteral("foo"), StringLiteral("bar")},
+                    {literal("foo"), literal("bar")},
                 ),
                 BoundIn[int](
                     BoundReference(
                         field=NestedField(field_id=2, name="bar", field_type=IntegerType(), required=True),
                         accessor=Accessor(position=1, inner=None),
                     ),
-                    {LongLiteral(1), LongLiteral(2), LongLiteral(3)},
+                    {literal(1), literal(2), literal(3)},
                 ),
             ),
         ),
         (
             Or(
-                In(Reference("foo"), (literal("bar"), literal("baz"))),
+                In(Reference("foo"), ("bar", "baz")),
                 In(
                     Reference("foo"),
-                    (literal("bar"),),
+                    ("bar",),
                 ),
                 In(
                     Reference("foo"),
-                    (literal("baz"),),
+                    ("baz",),
                 ),
             ),
             Or(
                 Or(
-                    BoundIn[str](
+                    BoundIn(
                         BoundReference(
                             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
                             accessor=Accessor(position=0, inner=None),
                         ),
-                        {StringLiteral("bar"), StringLiteral("baz")},
+                        {literal("bar"), literal("baz")},
                     ),
-                    BoundIn[str](
+                    BoundIn(
                         BoundReference(
                             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
                             accessor=Accessor(position=0, inner=None),
                         ),
-                        {StringLiteral("bar")},
+                        {literal("bar")},
                     ),
                 ),
-                BoundIn[str](
+                BoundIn(
                     BoundReference(
                         field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
                         accessor=Accessor(position=0, inner=None),
                     ),
-                    {StringLiteral("baz")},
+                    {literal("baz")},
                 ),
             ),
         ),
@@ -501,36 +489,36 @@ def test_or_expression_binding(unbound_or_expression, expected_bound_expression,
     "unbound_in_expression,expected_bound_expression",
     [
         (
-            In(Reference("foo"), (literal("foo"), literal("bar"))),
-            BoundIn[str](
-                BoundReference[str](
+            In(Reference("foo"), ("foo", "bar")),
+            BoundIn(
+                BoundReference(
                     field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
                     accessor=Accessor(position=0, inner=None),
                 ),
-                {StringLiteral("foo"), StringLiteral("bar")},
+                {literal("foo"), literal("bar")},
             ),
         ),
         (
-            In(Reference("foo"), (literal("bar"), literal("baz"))),
-            BoundIn[str](
-                BoundReference[str](
+            In(Reference("foo"), ("bar", "baz")),
+            BoundIn(
+                BoundReference(
                     field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
                     accessor=Accessor(position=0, inner=None),
                 ),
-                {StringLiteral("bar"), StringLiteral("baz")},
+                {literal("bar"), literal("baz")},
             ),
         ),
         (
             In(
                 Reference("foo"),
-                (literal("bar"),),
+                ("bar",),
             ),
             BoundEqualTo(
-                BoundReference[str](
+                BoundReference(
                     field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
                     accessor=Accessor(position=0, inner=None),
                 ),
-                StringLiteral("bar"),
+                literal("bar"),
             ),
         ),
     ],
@@ -545,39 +533,39 @@ def test_in_expression_binding(unbound_in_expression, expected_bound_expression,
     "unbound_not_expression,expected_bound_expression",
     [
         (
-            Not(In(Reference("foo"), (literal("foo"), literal("bar")))),
+            Not(In(Reference("foo"), ("foo", "bar"))),
             Not(
-                BoundIn[str](
-                    BoundReference[str](
+                BoundIn(
+                    BoundReference(
                         field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
                         accessor=Accessor(position=0, inner=None),
                     ),
-                    {StringLiteral("foo"), StringLiteral("bar")},
+                    {literal("foo"), literal("bar")},
                 )
             ),
         ),
         (
             Not(
                 Or(
-                    In(Reference("foo"), (literal("foo"), literal("bar"))),
-                    In(Reference("foo"), (literal("foo"), literal("bar"), literal("baz"))),
+                    In(Reference("foo"), ("foo", "bar")),
+                    In(Reference("foo"), ("foo", "bar", "baz")),
                 )
             ),
             Not(
                 Or(
-                    BoundIn[str](
+                    BoundIn(
                         BoundReference(
                             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
                             accessor=Accessor(position=0, inner=None),
                         ),
-                        {StringLiteral("foo"), StringLiteral("bar")},
+                        {literal("foo"), literal("bar")},
                     ),
-                    BoundIn[str](
+                    BoundIn(
                         BoundReference(
                             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
                             accessor=Accessor(position=0, inner=None),
                         ),
-                        {StringLiteral("foo"), StringLiteral("bar"), StringLiteral("baz")},
+                        {literal("foo"), literal("bar"), literal("baz")},
                     ),
                 ),
             ),
@@ -593,19 +581,19 @@ def test_not_expression_binding(unbound_not_expression, expected_bound_expressio
 def test_bound_boolean_expression_visitor_and_in():
     """Test visiting an And and In expression with a bound boolean expression visitor"""
     bound_expression = And(
-        BoundIn[str](
+        BoundIn(
             term=BoundReference(
                 field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
                 accessor=Accessor(position=0, inner=None),
             ),
-            literals=(StringLiteral("foo"), StringLiteral("bar")),
+            literals={literal("foo"), literal("bar")},
         ),
-        BoundIn[str](
+        BoundIn(
             term=BoundReference(
                 field=NestedField(field_id=2, name="bar", field_type=StringType(), required=False),
                 accessor=Accessor(position=1, inner=None),
             ),
-            literals=(StringLiteral("baz"), StringLiteral("qux")),
+            literals={literal("baz"), literal("qux")},
         ),
     )
     visitor = FooBoundBooleanExpressionVisitor()
@@ -617,21 +605,21 @@ def test_bound_boolean_expression_visitor_or():
     """Test visiting an Or expression with a bound boolean expression visitor"""
     bound_expression = Or(
         Not(
-            BoundIn[str](
-                BoundReference[str](
+            BoundIn(
+                BoundReference(
                     field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
                     accessor=Accessor(position=0, inner=None),
                 ),
-                {StringLiteral("foo"), StringLiteral("bar")},
+                {literal("foo"), literal("bar")},
             )
         ),
         Not(
-            BoundIn[str](
-                BoundReference[str](
+            BoundIn(
+                BoundReference(
                     field=NestedField(field_id=2, name="bar", field_type=StringType(), required=False),
                     accessor=Accessor(position=1, inner=None),
                 ),
-                {StringLiteral("baz"), StringLiteral("qux")},
+                {literal("baz"), literal("qux")},
             )
         ),
     )
@@ -641,12 +629,12 @@ def test_bound_boolean_expression_visitor_or():
 
 
 def test_bound_boolean_expression_visitor_equal():
-    bound_expression = BoundEqualTo[str](
+    bound_expression = BoundEqualTo(
         term=BoundReference(
             field=NestedField(field_id=2, name="bar", field_type=StringType(), required=False),
             accessor=Accessor(position=1, inner=None),
         ),
-        literal=StringLiteral("foo"),
+        literal=literal("foo"),
     )
     visitor = FooBoundBooleanExpressionVisitor()
     result = visit(bound_expression, visitor=visitor)
@@ -654,12 +642,12 @@ def test_bound_boolean_expression_visitor_equal():
 
 
 def test_bound_boolean_expression_visitor_not_equal():
-    bound_expression = BoundNotEqualTo[str](
+    bound_expression = BoundNotEqualTo(
         term=BoundReference(
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
         ),
-        literal=StringLiteral("foo"),
+        literal=literal("foo"),
     )
     visitor = FooBoundBooleanExpressionVisitor()
     result = visit(bound_expression, visitor=visitor)
@@ -681,12 +669,12 @@ def test_bound_boolean_expression_visitor_always_false():
 
 
 def test_bound_boolean_expression_visitor_in():
-    bound_expression = BoundIn[str](
+    bound_expression = BoundIn(
         term=BoundReference(
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
         ),
-        literals=(StringLiteral("foo"), StringLiteral("bar")),
+        literals={literal("foo"), literal("bar")},
     )
     visitor = FooBoundBooleanExpressionVisitor()
     result = visit(bound_expression, visitor=visitor)
@@ -694,12 +682,12 @@ def test_bound_boolean_expression_visitor_in():
 
 
 def test_bound_boolean_expression_visitor_not_in():
-    bound_expression = BoundNotIn[str](
+    bound_expression = BoundNotIn(
         term=BoundReference(
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
         ),
-        literals=(StringLiteral("foo"), StringLiteral("bar")),
+        literals={literal("foo"), literal("bar")},
     )
     visitor = FooBoundBooleanExpressionVisitor()
     result = visit(bound_expression, visitor=visitor)
@@ -707,7 +695,7 @@ def test_bound_boolean_expression_visitor_not_in():
 
 
 def test_bound_boolean_expression_visitor_is_nan():
-    bound_expression = BoundIsNaN[str](
+    bound_expression = BoundIsNaN(
         term=BoundReference(
             field=NestedField(field_id=3, name="baz", field_type=FloatType(), required=False),
             accessor=Accessor(position=0, inner=None),
@@ -719,7 +707,7 @@ def test_bound_boolean_expression_visitor_is_nan():
 
 
 def test_bound_boolean_expression_visitor_not_nan():
-    bound_expression = BoundNotNaN[str](
+    bound_expression = BoundNotNaN(
         term=BoundReference(
             field=NestedField(field_id=3, name="baz", field_type=FloatType(), required=False),
             accessor=Accessor(position=0, inner=None),
@@ -731,7 +719,7 @@ def test_bound_boolean_expression_visitor_not_nan():
 
 
 def test_bound_boolean_expression_visitor_is_null():
-    bound_expression = BoundIsNull[str](
+    bound_expression = BoundIsNull(
         term=BoundReference(
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
@@ -743,7 +731,7 @@ def test_bound_boolean_expression_visitor_is_null():
 
 
 def test_bound_boolean_expression_visitor_not_null():
-    bound_expression = BoundNotNull[str](
+    bound_expression = BoundNotNull(
         term=BoundReference(
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
@@ -755,12 +743,12 @@ def test_bound_boolean_expression_visitor_not_null():
 
 
 def test_bound_boolean_expression_visitor_greater_than():
-    bound_expression = BoundGreaterThan[str](
+    bound_expression = BoundGreaterThan(
         term=BoundReference(
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
         ),
-        literal=StringLiteral("foo"),
+        literal=literal("foo"),
     )
     visitor = FooBoundBooleanExpressionVisitor()
     result = visit(bound_expression, visitor=visitor)
@@ -768,12 +756,12 @@ def test_bound_boolean_expression_visitor_greater_than():
 
 
 def test_bound_boolean_expression_visitor_greater_than_or_equal():
-    bound_expression = BoundGreaterThanOrEqual[str](
+    bound_expression = BoundGreaterThanOrEqual(
         term=BoundReference(
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
         ),
-        literal=StringLiteral("foo"),
+        literal=literal("foo"),
     )
     visitor = FooBoundBooleanExpressionVisitor()
     result = visit(bound_expression, visitor=visitor)
@@ -781,12 +769,12 @@ def test_bound_boolean_expression_visitor_greater_than_or_equal():
 
 
 def test_bound_boolean_expression_visitor_less_than():
-    bound_expression = BoundLessThan[str](
+    bound_expression = BoundLessThan(
         term=BoundReference(
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
         ),
-        literal=StringLiteral("foo"),
+        literal=literal("foo"),
     )
     visitor = FooBoundBooleanExpressionVisitor()
     result = visit(bound_expression, visitor=visitor)
@@ -794,12 +782,12 @@ def test_bound_boolean_expression_visitor_less_than():
 
 
 def test_bound_boolean_expression_visitor_less_than_or_equal():
-    bound_expression = BoundLessThanOrEqual[str](
+    bound_expression = BoundLessThanOrEqual(
         term=BoundReference(
             field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
             accessor=Accessor(position=0, inner=None),
         ),
-        literal=StringLiteral("foo"),
+        literal=literal("foo"),
     )
     visitor = FooBoundBooleanExpressionVisitor()
     result = visit(bound_expression, visitor=visitor)
@@ -807,9 +795,9 @@ def test_bound_boolean_expression_visitor_less_than_or_equal():
 
 
 def test_bound_boolean_expression_visitor_raise_on_unbound_predicate():
-    bound_expression = LessThanOrEqual[str](
+    bound_expression = LessThanOrEqual(
         term=Reference("foo"),
-        literal=StringLiteral("foo"),
+        literal="foo",
     )
     visitor = FooBoundBooleanExpressionVisitor()
     with pytest.raises(TypeError) as exc_info:
@@ -1047,16 +1035,16 @@ def test_not_nan(schema: Schema, manifest: ManifestFile) -> None:
 
 def test_missing_stats(schema: Schema, manifest_no_stats: ManifestFile):
     expressions: List[BooleanExpression] = [
-        LessThan(Reference[int]("id"), LongLiteral(5)),
-        LessThanOrEqual(Reference[int]("id"), LongLiteral(30)),
-        EqualTo(Reference[int]("id"), LongLiteral(70)),
-        GreaterThan(Reference[int]("id"), LongLiteral(78)),
-        GreaterThanOrEqual(Reference[int]("id"), LongLiteral(90)),
-        NotEqualTo(Reference[int]("id"), LongLiteral(101)),
-        IsNull(Reference[int]("id")),
-        NotNull(Reference[int]("id")),
-        IsNaN(Reference[float]("float")),
-        NotNaN(Reference[float]("float")),
+        LessThan(Reference("id"), 5),
+        LessThanOrEqual(Reference("id"), 30),
+        EqualTo(Reference("id"), 70),
+        GreaterThan(Reference("id"), 78),
+        GreaterThanOrEqual(Reference("id"), 90),
+        NotEqualTo(Reference("id"), 101),
+        IsNull(Reference("id")),
+        NotNull(Reference("id")),
+        IsNaN(Reference("float")),
+        NotNaN(Reference("float")),
     ]
 
     for expr in expressions:
@@ -1064,11 +1052,11 @@ def test_missing_stats(schema: Schema, manifest_no_stats: ManifestFile):
 
 
 def test_not(schema: Schema, manifest: ManifestFile):
-    assert _ManifestEvalVisitor(schema, Not(LessThan(Reference[int]("id"), LongLiteral(INT_MIN_VALUE - 25)))).eval(
+    assert _ManifestEvalVisitor(schema, Not(LessThan(Reference("id"), INT_MIN_VALUE - 25))).eval(
         manifest
     ), "Should read: not(false)"
 
-    assert not _ManifestEvalVisitor(schema, Not(GreaterThan(Reference[int]("id"), LongLiteral(INT_MIN_VALUE - 25)))).eval(
+    assert not _ManifestEvalVisitor(schema, Not(GreaterThan(Reference("id"), INT_MIN_VALUE - 25))).eval(
         manifest
     ), "Should skip: not(true)"
 
@@ -1077,24 +1065,24 @@ def test_and(schema: Schema, manifest: ManifestFile):
     assert not _ManifestEvalVisitor(
         schema,
         And(
-            LessThan(Reference[int]("id"), LongLiteral(INT_MIN_VALUE - 25)),
-            GreaterThanOrEqual(Reference[int]("id"), LongLiteral(INT_MIN_VALUE - 30)),
+            LessThan(Reference("id"), INT_MIN_VALUE - 25),
+            GreaterThanOrEqual(Reference("id"), INT_MIN_VALUE - 30),
         ),
     ).eval(manifest), "Should skip: and(false, true)"
 
     assert not _ManifestEvalVisitor(
         schema,
         And(
-            LessThan(Reference[int]("id"), LongLiteral(INT_MIN_VALUE - 25)),
-            GreaterThanOrEqual(Reference[int]("id"), LongLiteral(INT_MAX_VALUE + 1)),
+            LessThan(Reference("id"), INT_MIN_VALUE - 25),
+            GreaterThanOrEqual(Reference("id"), INT_MAX_VALUE + 1),
         ),
     ).eval(manifest), "Should skip: and(false, false)"
 
     assert _ManifestEvalVisitor(
         schema,
         And(
-            GreaterThan(Reference[int]("id"), LongLiteral(INT_MIN_VALUE - 25)),
-            LessThanOrEqual(Reference[int]("id"), LongLiteral(INT_MIN_VALUE)),
+            GreaterThan(Reference("id"), INT_MIN_VALUE - 25),
+            LessThanOrEqual(Reference("id"), INT_MIN_VALUE),
         ),
     ).eval(manifest), "Should read: and(true, true)"
 
@@ -1103,386 +1091,317 @@ def test_or(schema: Schema, manifest: ManifestFile):
     assert not _ManifestEvalVisitor(
         schema,
         Or(
-            LessThan(Reference[int]("id"), LongLiteral(INT_MIN_VALUE - 25)),
-            GreaterThanOrEqual(Reference[int]("id"), LongLiteral(INT_MAX_VALUE + 1)),
+            LessThan(Reference("id"), INT_MIN_VALUE - 25),
+            GreaterThanOrEqual(Reference("id"), INT_MAX_VALUE + 1),
         ),
     ).eval(manifest), "Should skip: or(false, false)"
 
     assert _ManifestEvalVisitor(
         schema,
         Or(
-            LessThan(Reference[int]("id"), LongLiteral(INT_MIN_VALUE - 25)),
-            GreaterThanOrEqual(Reference[int]("id"), LongLiteral(INT_MAX_VALUE - 19)),
+            LessThan(Reference("id"), INT_MIN_VALUE - 25),
+            GreaterThanOrEqual(Reference("id"), INT_MAX_VALUE - 19),
         ),
     ).eval(manifest), "Should read: or(false, true)"
 
 
 def test_integer_lt(schema: Schema, manifest: ManifestFile):
-    assert not _ManifestEvalVisitor(schema, LessThan(Reference[int]("id"), LongLiteral(INT_MIN_VALUE - 25))).eval(
+    assert not _ManifestEvalVisitor(schema, LessThan(Reference("id"), INT_MIN_VALUE - 25)).eval(
         manifest
     ), "Should not read: id range below lower bound (5 < 30)"
 
-    assert not _ManifestEvalVisitor(schema, LessThan(Reference[int]("id"), LongLiteral(INT_MIN_VALUE))).eval(
+    assert not _ManifestEvalVisitor(schema, LessThan(Reference("id"), INT_MIN_VALUE)).eval(
         manifest
     ), "Should not read: id range below lower bound (30 is not < 30)"
 
-    assert _ManifestEvalVisitor(schema, LessThan(Reference[int]("id"), LongLiteral(INT_MIN_VALUE + 1))).eval(
+    assert _ManifestEvalVisitor(schema, LessThan(Reference("id"), INT_MIN_VALUE + 1)).eval(
         manifest
     ), "Should read: one possible id"
 
-    assert _ManifestEvalVisitor(schema, LessThan(Reference[int]("id"), LongLiteral(INT_MAX_VALUE))).eval(
-        manifest
-    ), "Should read: may possible ids"
+    assert _ManifestEvalVisitor(schema, LessThan(Reference("id"), INT_MAX_VALUE)).eval(manifest), "Should read: may possible ids"
 
 
 def test_integer_lt_eq(schema: Schema, manifest: ManifestFile):
-    assert not _ManifestEvalVisitor(schema, LessThanOrEqual(Reference[int]("id"), LongLiteral(INT_MIN_VALUE - 25))).eval(
+    assert not _ManifestEvalVisitor(schema, LessThanOrEqual(Reference("id"), INT_MIN_VALUE - 25)).eval(
         manifest
     ), "Should not read: id range below lower bound (5 < 30)"
 
-    assert not _ManifestEvalVisitor(schema, LessThanOrEqual(Reference[int]("id"), LongLiteral(INT_MIN_VALUE - 1))).eval(
+    assert not _ManifestEvalVisitor(schema, LessThanOrEqual(Reference("id"), INT_MIN_VALUE - 1)).eval(
         manifest
     ), "Should not read: id range below lower bound (29 < 30)"
 
-    assert _ManifestEvalVisitor(schema, LessThanOrEqual(Reference[int]("id"), LongLiteral(INT_MIN_VALUE))).eval(
+    assert _ManifestEvalVisitor(schema, LessThanOrEqual(Reference("id"), INT_MIN_VALUE)).eval(
         manifest
     ), "Should read: one possible id"
 
-    assert _ManifestEvalVisitor(schema, LessThanOrEqual(Reference[int]("id"), LongLiteral(INT_MAX_VALUE))).eval(
+    assert _ManifestEvalVisitor(schema, LessThanOrEqual(Reference("id"), INT_MAX_VALUE)).eval(
         manifest
     ), "Should read: many possible ids"
 
 
 def test_integer_gt(schema: Schema, manifest: ManifestFile):
-    assert not _ManifestEvalVisitor(schema, GreaterThan(Reference[int]("id"), LongLiteral(INT_MAX_VALUE + 6))).eval(
+    assert not _ManifestEvalVisitor(schema, GreaterThan(Reference("id"), INT_MAX_VALUE + 6)).eval(
         manifest
     ), "Should not read: id range above upper bound (85 < 79)"
 
-    assert not _ManifestEvalVisitor(schema, GreaterThan(Reference[int]("id"), LongLiteral(INT_MAX_VALUE))).eval(
+    assert not _ManifestEvalVisitor(schema, GreaterThan(Reference("id"), INT_MAX_VALUE)).eval(
         manifest
     ), "Should not read: id range above upper bound (79 is not > 79)"
 
-    assert _ManifestEvalVisitor(schema, GreaterThan(Reference[int]("id"), LongLiteral(INT_MAX_VALUE - 1))).eval(
+    assert _ManifestEvalVisitor(schema, GreaterThan(Reference("id"), INT_MAX_VALUE - 1)).eval(
         manifest
     ), "Should read: one possible id"
 
-    assert _ManifestEvalVisitor(schema, GreaterThan(Reference[int]("id"), LongLiteral(INT_MAX_VALUE - 4))).eval(
+    assert _ManifestEvalVisitor(schema, GreaterThan(Reference("id"), INT_MAX_VALUE - 4)).eval(
         manifest
     ), "Should read: may possible ids"
 
 
 def test_integer_gt_eq(schema: Schema, manifest: ManifestFile):
-    assert not _ManifestEvalVisitor(schema, GreaterThanOrEqual(Reference[int]("id"), LongLiteral(INT_MAX_VALUE + 6))).eval(
+    assert not _ManifestEvalVisitor(schema, GreaterThanOrEqual(Reference("id"), INT_MAX_VALUE + 6)).eval(
         manifest
     ), "Should not read: id range above upper bound (85 < 79)"
 
-    assert not _ManifestEvalVisitor(schema, GreaterThanOrEqual(Reference[int]("id"), LongLiteral(INT_MAX_VALUE + 1))).eval(
+    assert not _ManifestEvalVisitor(schema, GreaterThanOrEqual(Reference("id"), INT_MAX_VALUE + 1)).eval(
         manifest
     ), "Should not read: id range above upper bound (80 > 79)"
 
-    assert _ManifestEvalVisitor(schema, GreaterThanOrEqual(Reference[int]("id"), LongLiteral(INT_MAX_VALUE))).eval(
+    assert _ManifestEvalVisitor(schema, GreaterThanOrEqual(Reference("id"), INT_MAX_VALUE)).eval(
         manifest
     ), "Should read: one possible id"
 
-    assert _ManifestEvalVisitor(schema, GreaterThanOrEqual(Reference[int]("id"), LongLiteral(INT_MAX_VALUE))).eval(
+    assert _ManifestEvalVisitor(schema, GreaterThanOrEqual(Reference("id"), INT_MAX_VALUE)).eval(
         manifest
     ), "Should read: may possible ids"
 
 
 def test_integer_eq(schema: Schema, manifest: ManifestFile):
-    assert not _ManifestEvalVisitor(schema, EqualTo(Reference[int]("id"), LongLiteral(INT_MIN_VALUE - 25))).eval(
+    assert not _ManifestEvalVisitor(schema, EqualTo(Reference("id"), INT_MIN_VALUE - 25)).eval(
         manifest
     ), "Should not read: id below lower bound"
 
-    assert not _ManifestEvalVisitor(schema, EqualTo(Reference[int]("id"), LongLiteral(INT_MIN_VALUE - 1))).eval(
+    assert not _ManifestEvalVisitor(schema, EqualTo(Reference("id"), INT_MIN_VALUE - 1)).eval(
         manifest
     ), "Should not read: id below lower bound"
 
-    assert _ManifestEvalVisitor(schema, EqualTo(Reference[int]("id"), LongLiteral(INT_MIN_VALUE))).eval(
+    assert _ManifestEvalVisitor(schema, EqualTo(Reference("id"), INT_MIN_VALUE)).eval(
         manifest
     ), "Should read: id equal to lower bound"
 
-    assert _ManifestEvalVisitor(schema, EqualTo(Reference[int]("id"), LongLiteral(INT_MAX_VALUE - 4))).eval(
+    assert _ManifestEvalVisitor(schema, EqualTo(Reference("id"), INT_MAX_VALUE - 4)).eval(
         manifest
     ), "Should read: id between lower and upper bounds"
 
-    assert _ManifestEvalVisitor(schema, EqualTo(Reference[int]("id"), LongLiteral(INT_MAX_VALUE))).eval(
+    assert _ManifestEvalVisitor(schema, EqualTo(Reference("id"), INT_MAX_VALUE)).eval(
         manifest
     ), "Should read: id equal to upper bound"
 
-    assert not _ManifestEvalVisitor(schema, EqualTo(Reference[int]("id"), LongLiteral(INT_MAX_VALUE + 1))).eval(
+    assert not _ManifestEvalVisitor(schema, EqualTo(Reference("id"), INT_MAX_VALUE + 1)).eval(
         manifest
     ), "Should not read: id above upper bound"
 
-    assert not _ManifestEvalVisitor(schema, EqualTo(Reference[int]("id"), LongLiteral(INT_MAX_VALUE + 6))).eval(
+    assert not _ManifestEvalVisitor(schema, EqualTo(Reference("id"), INT_MAX_VALUE + 6)).eval(
         manifest
     ), "Should not read: id above upper bound"
 
 
 def test_integer_not_eq(schema: Schema, manifest: ManifestFile):
-    assert _ManifestEvalVisitor(schema, NotEqualTo(Reference[int]("id"), LongLiteral(INT_MIN_VALUE - 25))).eval(
+    assert _ManifestEvalVisitor(schema, NotEqualTo(Reference("id"), INT_MIN_VALUE - 25)).eval(
         manifest
     ), "Should read: id below lower bound"
 
-    assert _ManifestEvalVisitor(schema, NotEqualTo(Reference[int]("id"), LongLiteral(INT_MIN_VALUE - 1))).eval(
+    assert _ManifestEvalVisitor(schema, NotEqualTo(Reference("id"), INT_MIN_VALUE - 1)).eval(
         manifest
     ), "Should read: id below lower bound"
 
-    assert _ManifestEvalVisitor(schema, NotEqualTo(Reference[int]("id"), LongLiteral(INT_MIN_VALUE))).eval(
+    assert _ManifestEvalVisitor(schema, NotEqualTo(Reference("id"), INT_MIN_VALUE)).eval(
         manifest
     ), "Should read: id equal to lower bound"
 
-    assert _ManifestEvalVisitor(schema, NotEqualTo(Reference[int]("id"), LongLiteral(INT_MAX_VALUE - 4))).eval(
+    assert _ManifestEvalVisitor(schema, NotEqualTo(Reference("id"), INT_MAX_VALUE - 4)).eval(
         manifest
     ), "Should read: id between lower and upper bounds"
 
-    assert _ManifestEvalVisitor(schema, NotEqualTo(Reference[int]("id"), LongLiteral(INT_MAX_VALUE))).eval(
+    assert _ManifestEvalVisitor(schema, NotEqualTo(Reference("id"), INT_MAX_VALUE)).eval(
         manifest
     ), "Should read: id equal to upper bound"
 
-    assert _ManifestEvalVisitor(schema, NotEqualTo(Reference[int]("id"), LongLiteral(INT_MAX_VALUE + 1))).eval(
+    assert _ManifestEvalVisitor(schema, NotEqualTo(Reference("id"), INT_MAX_VALUE + 1)).eval(
         manifest
     ), "Should read: id above upper bound"
 
-    assert _ManifestEvalVisitor(schema, NotEqualTo(Reference[int]("id"), LongLiteral(INT_MAX_VALUE + 6))).eval(
+    assert _ManifestEvalVisitor(schema, NotEqualTo(Reference("id"), INT_MAX_VALUE + 6)).eval(
         manifest
     ), "Should read: id above upper bound"
 
 
 def test_integer_not_eq_rewritten(schema: Schema, manifest: ManifestFile):
-    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference[int]("id"), LongLiteral(INT_MIN_VALUE - 25)))).eval(
+    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference("id"), INT_MIN_VALUE - 25))).eval(
         manifest
     ), "Should read: id below lower bound"
 
-    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference[int]("id"), LongLiteral(INT_MIN_VALUE - 1)))).eval(
+    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference("id"), INT_MIN_VALUE - 1))).eval(
         manifest
     ), "Should read: id below lower bound"
 
-    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference[int]("id"), LongLiteral(INT_MIN_VALUE)))).eval(
+    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference("id"), INT_MIN_VALUE))).eval(
         manifest
     ), "Should read: id equal to lower bound"
 
-    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference[int]("id"), LongLiteral(INT_MAX_VALUE - 4)))).eval(
+    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference("id"), INT_MAX_VALUE - 4))).eval(
         manifest
     ), "Should read: id between lower and upper bounds"
 
-    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference[int]("id"), LongLiteral(INT_MAX_VALUE)))).eval(
+    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference("id"), INT_MAX_VALUE))).eval(
         manifest
     ), "Should read: id equal to upper bound"
 
-    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference[int]("id"), LongLiteral(INT_MAX_VALUE + 1)))).eval(
+    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference("id"), INT_MAX_VALUE + 1))).eval(
         manifest
     ), "Should read: id above upper bound"
 
-    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference[int]("id"), LongLiteral(INT_MAX_VALUE + 6)))).eval(
+    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference("id"), INT_MAX_VALUE + 6))).eval(
         manifest
     ), "Should read: id above upper bound"
 
 
 def test_integer_not_eq_rewritten_case_insensitive(schema: Schema, manifest: ManifestFile):
-    assert _ManifestEvalVisitor(
-        schema, Not(EqualTo(Reference[int]("ID"), LongLiteral(INT_MIN_VALUE - 25))), case_sensitive=False
-    ).eval(manifest), "Should read: id below lower bound"
+    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference("ID"), INT_MIN_VALUE - 25)), case_sensitive=False).eval(
+        manifest
+    ), "Should read: id below lower bound"
 
-    assert _ManifestEvalVisitor(
-        schema, Not(EqualTo(Reference[int]("ID"), LongLiteral(INT_MIN_VALUE - 1))), case_sensitive=False
-    ).eval(manifest), "Should read: id below lower bound"
+    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference("ID"), INT_MIN_VALUE - 1)), case_sensitive=False).eval(
+        manifest
+    ), "Should read: id below lower bound"
 
-    assert _ManifestEvalVisitor(
-        schema, Not(EqualTo(Reference[int]("ID"), LongLiteral(INT_MIN_VALUE))), case_sensitive=False
-    ).eval(manifest), "Should read: id equal to lower bound"
+    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference("ID"), INT_MIN_VALUE)), case_sensitive=False).eval(
+        manifest
+    ), "Should read: id equal to lower bound"
 
-    assert _ManifestEvalVisitor(
-        schema, Not(EqualTo(Reference[int]("ID"), LongLiteral(INT_MAX_VALUE - 4))), case_sensitive=False
-    ).eval(manifest), "Should read: id between lower and upper bounds"
+    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference("ID"), INT_MAX_VALUE - 4)), case_sensitive=False).eval(
+        manifest
+    ), "Should read: id between lower and upper bounds"
 
-    assert _ManifestEvalVisitor(
-        schema, Not(EqualTo(Reference[int]("ID"), LongLiteral(INT_MAX_VALUE))), case_sensitive=False
-    ).eval(manifest), "Should read: id equal to upper bound"
+    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference("ID"), INT_MAX_VALUE)), case_sensitive=False).eval(
+        manifest
+    ), "Should read: id equal to upper bound"
 
-    assert _ManifestEvalVisitor(
-        schema, Not(EqualTo(Reference[int]("ID"), LongLiteral(INT_MAX_VALUE + 1))), case_sensitive=False
-    ).eval(manifest), "Should read: id above upper bound"
+    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference("ID"), INT_MAX_VALUE + 1)), case_sensitive=False).eval(
+        manifest
+    ), "Should read: id above upper bound"
 
-    assert _ManifestEvalVisitor(
-        schema, Not(EqualTo(Reference[int]("ID"), LongLiteral(INT_MAX_VALUE + 6))), case_sensitive=False
-    ).eval(manifest), "Should read: id above upper bound"
+    assert _ManifestEvalVisitor(schema, Not(EqualTo(Reference("ID"), INT_MAX_VALUE + 6)), case_sensitive=False).eval(
+        manifest
+    ), "Should read: id above upper bound"
 
 
 def test_integer_in(schema: Schema, manifest: ManifestFile):
-    assert not _ManifestEvalVisitor(
-        schema, In(Reference[int]("id"), (LongLiteral(INT_MIN_VALUE - 25), LongLiteral(INT_MIN_VALUE - 24)))
-    ).eval(manifest), "Should not read: id below lower bound (5 < 30, 6 < 30)"
+    assert not _ManifestEvalVisitor(schema, In(Reference("id"), (INT_MIN_VALUE - 25, INT_MIN_VALUE - 24))).eval(
+        manifest
+    ), "Should not read: id below lower bound (5 < 30, 6 < 30)"
 
-    assert not _ManifestEvalVisitor(
-        schema, In(Reference[int]("id"), (LongLiteral(INT_MIN_VALUE - 2), LongLiteral(INT_MIN_VALUE - 1)))
-    ).eval(manifest), "Should not read: id below lower bound (28 < 30, 29 < 30)"
+    assert not _ManifestEvalVisitor(schema, In(Reference("id"), (INT_MIN_VALUE - 2, INT_MIN_VALUE - 1))).eval(
+        manifest
+    ), "Should not read: id below lower bound (28 < 30, 29 < 30)"
 
-    assert _ManifestEvalVisitor(
-        schema, In(Reference[int]("id"), (LongLiteral(INT_MIN_VALUE - 1), LongLiteral(INT_MIN_VALUE)))
-    ).eval(manifest), "Should read: id equal to lower bound (30 == 30)"
+    assert _ManifestEvalVisitor(schema, In(Reference("id"), (INT_MIN_VALUE - 1, INT_MIN_VALUE))).eval(
+        manifest
+    ), "Should read: id equal to lower bound (30 == 30)"
 
-    assert _ManifestEvalVisitor(
-        schema, In(Reference[int]("id"), (LongLiteral(INT_MAX_VALUE - 4), LongLiteral(INT_MAX_VALUE - 3)))
-    ).eval(manifest), "Should read: id between lower and upper bounds (30 < 75 < 79, 30 < 76 < 79)"
+    assert _ManifestEvalVisitor(schema, In(Reference("id"), (INT_MAX_VALUE - 4, INT_MAX_VALUE - 3))).eval(
+        manifest
+    ), "Should read: id between lower and upper bounds (30 < 75 < 79, 30 < 76 < 79)"
 
-    assert _ManifestEvalVisitor(
-        schema, In(Reference[int]("id"), (LongLiteral(INT_MAX_VALUE), LongLiteral(INT_MAX_VALUE + 1)))
-    ).eval(manifest), "Should read: id equal to upper bound (79 == 79)"
+    assert _ManifestEvalVisitor(schema, In(Reference("id"), (INT_MAX_VALUE, INT_MAX_VALUE + 1))).eval(
+        manifest
+    ), "Should read: id equal to upper bound (79 == 79)"
 
-    assert not _ManifestEvalVisitor(
-        schema, In(Reference[int]("id"), (LongLiteral(INT_MAX_VALUE + 1), LongLiteral(INT_MAX_VALUE + 2)))
-    ).eval(manifest), "Should not read: id above upper bound (80 > 79, 81 > 79)"
+    assert not _ManifestEvalVisitor(schema, In(Reference("id"), (INT_MAX_VALUE + 1, INT_MAX_VALUE + 2))).eval(
+        manifest
+    ), "Should not read: id above upper bound (80 > 79, 81 > 79)"
 
-    assert not _ManifestEvalVisitor(
-        schema, In(Reference[int]("id"), (LongLiteral(INT_MAX_VALUE + 6), LongLiteral(INT_MAX_VALUE + 7)))
-    ).eval(manifest), "Should not read: id above upper bound (85 > 79, 86 > 79)"
+    assert not _ManifestEvalVisitor(schema, In(Reference("id"), (INT_MAX_VALUE + 6, INT_MAX_VALUE + 7))).eval(
+        manifest
+    ), "Should not read: id above upper bound (85 > 79, 86 > 79)"
 
-    assert not _ManifestEvalVisitor(
-        schema, In(Reference[str]("all_nulls_missing_nan"), (StringLiteral("abc"), StringLiteral("def")))
-    ).eval(manifest), "Should skip: in on all nulls column"
+    assert not _ManifestEvalVisitor(schema, In(Reference("all_nulls_missing_nan"), ("abc", "def"))).eval(
+        manifest
+    ), "Should skip: in on all nulls column"
 
-    assert _ManifestEvalVisitor(schema, In(Reference[str]("some_nulls"), (StringLiteral("abc"), StringLiteral("def")))).eval(
+    assert _ManifestEvalVisitor(schema, In(Reference("some_nulls"), ("abc", "def"))).eval(
         manifest
     ), "Should read: in on some nulls column"
 
-    assert _ManifestEvalVisitor(schema, In(Reference[str]("no_nulls"), (StringLiteral("abc"), StringLiteral("def")))).eval(
+    assert _ManifestEvalVisitor(schema, In(Reference("no_nulls"), ("abc", "def"))).eval(
         manifest
     ), "Should read: in on no nulls column"
 
 
 def test_integer_not_in(schema: Schema, manifest: ManifestFile):
-    assert _ManifestEvalVisitor(
-        schema, NotIn(Reference[int]("id"), (LongLiteral(INT_MIN_VALUE - 25), LongLiteral(INT_MIN_VALUE - 24)))
-    ).eval(manifest), "Should read: id below lower bound (5 < 30, 6 < 30)"
+    assert _ManifestEvalVisitor(schema, NotIn(Reference("id"), (INT_MIN_VALUE - 25, INT_MIN_VALUE - 24))).eval(
+        manifest
+    ), "Should read: id below lower bound (5 < 30, 6 < 30)"
 
-    assert _ManifestEvalVisitor(
-        schema, NotIn(Reference[int]("id"), (LongLiteral(INT_MIN_VALUE - 2), LongLiteral(INT_MIN_VALUE - 1)))
-    ).eval(manifest), "Should read: id below lower bound (28 < 30, 29 < 30)"
+    assert _ManifestEvalVisitor(schema, NotIn(Reference("id"), (INT_MIN_VALUE - 2, INT_MIN_VALUE - 1))).eval(
+        manifest
+    ), "Should read: id below lower bound (28 < 30, 29 < 30)"
 
-    assert _ManifestEvalVisitor(
-        schema, NotIn(Reference[int]("id"), (LongLiteral(INT_MIN_VALUE - 1), LongLiteral(INT_MIN_VALUE)))
-    ).eval(manifest), "Should read: id equal to lower bound (30 == 30)"
+    assert _ManifestEvalVisitor(schema, NotIn(Reference("id"), (INT_MIN_VALUE - 1, INT_MIN_VALUE))).eval(
+        manifest
+    ), "Should read: id equal to lower bound (30 == 30)"
 
-    assert _ManifestEvalVisitor(
-        schema, NotIn(Reference[int]("id"), (LongLiteral(INT_MAX_VALUE - 4), LongLiteral(INT_MAX_VALUE - 3)))
-    ).eval(manifest), "Should read: id between lower and upper bounds (30 < 75 < 79, 30 < 76 < 79)"
+    assert _ManifestEvalVisitor(schema, NotIn(Reference("id"), (INT_MAX_VALUE - 4, INT_MAX_VALUE - 3))).eval(
+        manifest
+    ), "Should read: id between lower and upper bounds (30 < 75 < 79, 30 < 76 < 79)"
 
-    assert _ManifestEvalVisitor(
-        schema, NotIn(Reference[int]("id"), (LongLiteral(INT_MAX_VALUE), LongLiteral(INT_MAX_VALUE + 1)))
-    ).eval(manifest), "Should read: id equal to upper bound (79 == 79)"
+    assert _ManifestEvalVisitor(schema, NotIn(Reference("id"), (INT_MAX_VALUE, INT_MAX_VALUE + 1))).eval(
+        manifest
+    ), "Should read: id equal to upper bound (79 == 79)"
 
-    assert _ManifestEvalVisitor(
-        schema, NotIn(Reference[int]("id"), (LongLiteral(INT_MAX_VALUE + 1), LongLiteral(INT_MAX_VALUE + 2)))
-    ).eval(manifest), "Should read: id above upper bound (80 > 79, 81 > 79)"
+    assert _ManifestEvalVisitor(schema, NotIn(Reference("id"), (INT_MAX_VALUE + 1, INT_MAX_VALUE + 2))).eval(
+        manifest
+    ), "Should read: id above upper bound (80 > 79, 81 > 79)"
 
-    assert _ManifestEvalVisitor(
-        schema, NotIn(Reference[int]("id"), (LongLiteral(INT_MAX_VALUE + 6), LongLiteral(INT_MAX_VALUE + 7)))
-    ).eval(manifest), "Should read: id above upper bound (85 > 79, 86 > 79)"
+    assert _ManifestEvalVisitor(schema, NotIn(Reference("id"), (INT_MAX_VALUE + 6, INT_MAX_VALUE + 7))).eval(
+        manifest
+    ), "Should read: id above upper bound (85 > 79, 86 > 79)"
 
-    assert _ManifestEvalVisitor(
-        schema, NotIn(Reference[str]("all_nulls_missing_nan"), (StringLiteral("abc"), StringLiteral("def")))
-    ).eval(manifest), "Should read: notIn on no nulls column"
+    assert _ManifestEvalVisitor(schema, NotIn(Reference("all_nulls_missing_nan"), ("abc", "def"))).eval(
+        manifest
+    ), "Should read: notIn on no nulls column"
 
-    assert _ManifestEvalVisitor(schema, NotIn(Reference[str]("some_nulls"), (StringLiteral("abc"), StringLiteral("def")))).eval(
+    assert _ManifestEvalVisitor(schema, NotIn(Reference("some_nulls"), ("abc", "def"))).eval(
         manifest
     ), "Should read: in on some nulls column"
 
-    assert _ManifestEvalVisitor(schema, NotIn(Reference[str]("no_nulls"), (StringLiteral("abc"), StringLiteral("def")))).eval(
+    assert _ManifestEvalVisitor(schema, NotIn(Reference("no_nulls"), ("abc", "def"))).eval(
         manifest
     ), "Should read: in on no nulls column"
 
 
-def test_bound_predicate_invert():
-    with pytest.raises(NotImplementedError):
-        _ = ~BoundPredicate(
-            term=BoundReference(
-                field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
-                accessor=Accessor(position=0, inner=None),
-            )
-        )
-
-
-def test_bound_unary_predicate_invert():
-    with pytest.raises(NotImplementedError):
-        _ = ~BoundUnaryPredicate(
-            term=BoundReference(
-                field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
-                accessor=Accessor(position=0, inner=None),
-            )
-        )
-
-
-def test_bound_set_predicate_invert():
-    with pytest.raises(NotImplementedError):
-        _ = ~BoundSetPredicate(
-            term=BoundReference(
-                field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
-                accessor=Accessor(position=0, inner=None),
-            ),
-            literals={literal("hello"), literal("world")},
-        )
-
-
-def test_bound_literal_predicate_invert():
-    with pytest.raises(NotImplementedError):
-        _ = ~BoundLiteralPredicate(
-            term=BoundReference(
-                field=NestedField(field_id=1, name="foo", field_type=StringType(), required=False),
-                accessor=Accessor(position=0, inner=None),
-            ),
-            literal=literal("world"),
-        )
-
-
-def test_unbound_predicate_invert():
-    with pytest.raises(NotImplementedError):
-        _ = ~UnboundPredicate(term=Reference("a"))
-
-
-def test_unbound_predicate_bind(table_schema_simple: Schema):
-    with pytest.raises(NotImplementedError):
-        _ = UnboundPredicate(term=Reference("a")).bind(table_schema_simple)
-
-
-def test_unbound_unary_predicate_invert():
-    with pytest.raises(NotImplementedError):
-        _ = ~UnaryPredicate(term=Reference("a"))
-
-
-def test_unbound_set_predicate_invert():
-    with pytest.raises(NotImplementedError):
-        _ = ~SetPredicate(term=Reference("a"), literals=(literal("hello"), literal("world")))
-
-
-def test_unbound_literal_predicate_invert():
-    with pytest.raises(NotImplementedError):
-        _ = ~LiteralPredicate(term=Reference("a"), literal=literal("hello"))
-
-
 def test_rewrite_not_equal_to():
-    assert rewrite_not(Not(EqualTo(Reference("x"), literal(34.56)))) == NotEqualTo(Reference("x"), literal(34.56))
+    assert rewrite_not(Not(EqualTo(Reference("x"), 34.56))) == NotEqualTo(Reference("x"), 34.56)
 
 
 def test_rewrite_not_not_equal_to():
-    assert rewrite_not(Not(NotEqualTo(Reference("x"), literal(34.56)))) == EqualTo(Reference("x"), literal(34.56))
+    assert rewrite_not(Not(NotEqualTo(Reference("x"), 34.56))) == EqualTo(Reference("x"), 34.56)
 
 
 def test_rewrite_not_in():
-    assert rewrite_not(Not(In(Reference("x"), (literal(34.56),)))) == NotIn(Reference("x"), (literal(34.56),))
+    assert rewrite_not(Not(In(Reference("x"), (34.56,)))) == NotIn(Reference("x"), (34.56,))
 
 
 def test_rewrite_and():
-    assert rewrite_not(Not(And(EqualTo(Reference("x"), literal(34.56)), EqualTo(Reference("y"), literal(34.56)),))) == Or(
-        NotEqualTo(term=Reference(name="x"), literal=literal(34.56)),
-        NotEqualTo(term=Reference(name="y"), literal=literal(34.56)),
+    assert rewrite_not(Not(And(EqualTo(Reference("x"), 34.56), EqualTo(Reference("y"), 34.56),))) == Or(
+        NotEqualTo(term=Reference(name="x"), literal=34.56),
+        NotEqualTo(term=Reference(name="y"), literal=34.56),
     )
 
 
 def test_rewrite_or():
-    assert rewrite_not(Not(Or(EqualTo(Reference("x"), literal(34.56)), EqualTo(Reference("y"), literal(34.56)),))) == And(
-        NotEqualTo(term=Reference(name="x"), literal=literal(34.56)),
-        NotEqualTo(term=Reference(name="y"), literal=literal(34.56)),
+    assert rewrite_not(Not(Or(EqualTo(Reference("x"), 34.56), EqualTo(Reference("y"), 34.56),))) == And(
+        NotEqualTo(term=Reference(name="x"), literal=34.56),
+        NotEqualTo(term=Reference(name="y"), literal=34.56),
     )
 
 
diff --git a/python/tests/table/test_init.py b/python/tests/table/test_init.py
index e20320701c..8e4dc7820e 100644
--- a/python/tests/table/test_init.py
+++ b/python/tests/table/test_init.py
@@ -199,18 +199,18 @@ def test_table_scan_select(table: Table):
 def test_table_scan_row_filter(table: Table):
     scan = table.scan()
     assert scan.row_filter == AlwaysTrue()
-    assert scan.filter_rows(EqualTo("x", 10)).row_filter == EqualTo("x", 10)  # type: ignore
-    assert scan.filter_rows(EqualTo("x", 10)).filter_rows(In("y", (10, 11))).row_filter == And(  # type: ignore
-        EqualTo("x", 10), In("y", (10, 11))  # type: ignore
+    assert scan.filter_rows(EqualTo("x", 10)).row_filter == EqualTo("x", 10)
+    assert scan.filter_rows(EqualTo("x", 10)).filter_rows(In("y", (10, 11))).row_filter == And(
+        EqualTo("x", 10), In("y", (10, 11))
     )
 
 
 def test_table_scan_partition_filter(table: Table):
     scan = table.scan()
     assert scan.row_filter == AlwaysTrue()
-    assert scan.filter_partitions(EqualTo("x", 10)).partition_filter == EqualTo("x", 10)  # type: ignore
-    assert scan.filter_partitions(EqualTo("x", 10)).filter_partitions(In("y", (10, 11))).partition_filter == And(  # type: ignore
-        EqualTo("x", 10), In("y", (10, 11))  # type: ignore
+    assert scan.filter_partitions(EqualTo("x", 10)).partition_filter == EqualTo("x", 10)
+    assert scan.filter_partitions(EqualTo("x", 10)).filter_partitions(In("y", (10, 11))).partition_filter == And(
+        EqualTo("x", 10), In("y", (10, 11))
     )