You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by gi...@apache.org on 2026/04/15 23:09:55 UTC

(datafusion) branch main updated: Remove `as_any` on the `PhysicalExpr` trait (#21573)

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

github-bot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git


The following commit(s) were added to refs/heads/main by this push:
     new dc6142ea49 Remove `as_any` on the `PhysicalExpr` trait (#21573)
dc6142ea49 is described below

commit dc6142ea4927e0853cf3a671e7055c41a1cad659
Author: Tim Saucer <ti...@gmail.com>
AuthorDate: Wed Apr 15 19:07:43 2026 -0400

    Remove `as_any` on the `PhysicalExpr` trait (#21573)
    
    ## Which issue does this PR close?
    
    - Addresses part of https://github.com/apache/datafusion/issues/21572
    
    ## Rationale for this change
    
    This PR reduces the amount of boilerplate code that users need to write
    for Physical Expressions.
    
    ## What changes are included in this PR?
    
    Now that we have [trait
    upcasting](https://blog.rust-lang.org/2025/04/03/Rust-1.86.0/) since
    rust 1.86, we no longer need every implementation of this trait to have
    the as_any function that returns &self. This PR makes Any an supertrait
    and makes the appropriate casts when necessary.
    
    ## Are these changes tested?
    
    Existing unit tests.
    
    ## Are there any user-facing changes?
    
    Yes, the users simply need to remove the as_any function. The upgrade
    guide is updated.
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 (1M context) <no...@anthropic.com>
---
 .../custom_data_source/custom_file_casts.rs        |   2 +-
 .../examples/data_io/json_shredding.rs             |  10 +-
 .../examples/proto/expression_deduplication.rs     |   1 -
 datafusion/core/src/datasource/mod.rs              |   2 +-
 .../core/tests/fuzz_cases/equivalence/utils.rs     |  10 +-
 .../fuzz_cases/sort_preserving_repartition_fuzz.rs |   8 +-
 datafusion/core/tests/parquet/expr_adapter.rs      |   2 +-
 .../tests/physical_optimizer/filter_pushdown.rs    |   2 +-
 .../tests/physical_optimizer/join_selection.rs     |   1 -
 .../replace_with_order_preserving_variants.rs      |   4 +-
 datafusion/datasource-parquet/src/metadata.rs      |  16 +--
 datafusion/datasource-parquet/src/opener.rs        |   6 +-
 datafusion/datasource-parquet/src/row_filter.rs    |  15 +-
 .../datasource-parquet/src/supported_predicates.rs |   7 +-
 datafusion/datasource/src/file_scan_config/mod.rs  |  13 +-
 .../src/file_scan_config/sort_pushdown.rs          |   4 +-
 datafusion/datasource/src/projection.rs            |   6 +-
 datafusion/datasource/src/statistics.rs            |   2 +-
 datafusion/execution/src/cache/cache_unit.rs       |   4 -
 datafusion/ffi/src/physical_expr/mod.rs            |   7 +-
 datafusion/functions-aggregate/src/count.rs        |   7 +-
 datafusion/functions-aggregate/src/min_max.rs      |   5 +-
 datafusion/functions-aggregate/src/nth_value.rs    |   1 -
 datafusion/functions-aggregate/src/string_agg.rs   |   2 +-
 datafusion/functions-window/src/lead_lag.rs        |   3 +-
 datafusion/functions-window/src/utils.rs           |   1 -
 .../physical-expr-adapter/src/schema_rewriter.rs   |  30 ++--
 .../physical-expr-common/src/physical_expr.rs      |  32 +++--
 datafusion/physical-expr-common/src/sort_expr.rs   |   1 -
 datafusion/physical-expr/src/analysis.rs           |   4 +-
 .../physical-expr/src/async_scalar_function.rs     |  12 +-
 datafusion/physical-expr/src/equivalence/class.rs  |  11 +-
 .../src/equivalence/properties/mod.rs              |  18 ++-
 datafusion/physical-expr/src/expressions/binary.rs |  12 +-
 datafusion/physical-expr/src/expressions/case.rs   |  23 ++-
 .../expressions/case/literal_lookup_table/mod.rs   |   6 +-
 datafusion/physical-expr/src/expressions/cast.rs   |   6 -
 datafusion/physical-expr/src/expressions/column.rs |   8 +-
 .../src/expressions/dynamic_filters.rs             |   8 +-
 .../physical-expr/src/expressions/in_list.rs       |   6 -
 .../physical-expr/src/expressions/is_not_null.rs   |   7 +-
 .../physical-expr/src/expressions/is_null.rs       |   7 +-
 datafusion/physical-expr/src/expressions/like.rs   |   6 +-
 .../physical-expr/src/expressions/literal.rs       |   6 -
 .../physical-expr/src/expressions/negative.rs      |   6 -
 datafusion/physical-expr/src/expressions/no_op.rs  |   6 -
 datafusion/physical-expr/src/expressions/not.rs    |   6 -
 .../physical-expr/src/expressions/try_cast.rs      |   6 -
 .../src/expressions/unknown_column.rs              |   6 -
 .../physical-expr/src/intervals/cp_solver.rs       |  19 +--
 datafusion/physical-expr/src/intervals/utils.rs    |  11 +-
 datafusion/physical-expr/src/physical_expr.rs      |   7 +-
 datafusion/physical-expr/src/planner.rs            |   1 -
 datafusion/physical-expr/src/projection.rs         |  35 ++---
 datafusion/physical-expr/src/scalar_function.rs    |  14 +-
 .../src/simplifier/const_evaluator.rs              |  15 +-
 datafusion/physical-expr/src/simplifier/mod.rs     |  31 ++---
 datafusion/physical-expr/src/simplifier/not.rs     |  10 +-
 .../physical-expr/src/simplifier/unwrap_cast.rs    | 109 +++++----------
 .../physical-expr/src/statistics/stats_solver.rs   |   2 +-
 datafusion/physical-expr/src/utils/guarantee.rs    |  28 ++--
 datafusion/physical-expr/src/utils/mod.rs          |  23 ++-
 .../src/enforce_sorting/sort_pushdown.rs           |  14 +-
 .../physical-optimizer/src/projection_pushdown.rs  |   8 +-
 .../physical-optimizer/src/topk_aggregation.rs     |   5 +-
 datafusion/physical-plan/src/aggregates/mod.rs     |   8 +-
 datafusion/physical-plan/src/async_func.rs         |   3 +-
 datafusion/physical-plan/src/column_rewriter.rs    |   2 +-
 datafusion/physical-plan/src/filter.rs             |   6 +-
 datafusion/physical-plan/src/filter_pushdown.rs    |   2 +-
 .../physical-plan/src/joins/hash_join/exec.rs      |  10 +-
 .../src/joins/hash_join/partitioned_hash_eval.rs   |  10 +-
 .../physical-plan/src/joins/stream_join_utils.rs   |   2 +-
 datafusion/physical-plan/src/joins/utils.rs        |   5 +-
 .../physical-plan/src/operator_statistics/mod.rs   |   4 +-
 datafusion/physical-plan/src/projection.rs         |  23 ++-
 datafusion/proto/src/physical_plan/to_proto.rs     |   5 +-
 .../proto/tests/cases/roundtrip_physical_plan.rs   |  20 +--
 datafusion/pruning/src/pruning_predicate.rs        |  58 +++-----
 docs/source/library-user-guide/upgrading/54.0.0.md | 155 ++-------------------
 80 files changed, 295 insertions(+), 724 deletions(-)

diff --git a/datafusion-examples/examples/custom_data_source/custom_file_casts.rs b/datafusion-examples/examples/custom_data_source/custom_file_casts.rs
index 6db0cda565..71addc6d1b 100644
--- a/datafusion-examples/examples/custom_data_source/custom_file_casts.rs
+++ b/datafusion-examples/examples/custom_data_source/custom_file_casts.rs
@@ -185,7 +185,7 @@ impl PhysicalExprAdapter for CustomCastsPhysicalExprAdapter {
         // kernel / expression. For example, DataFusion Comet has a custom cast
         // kernel in its native Spark expression implementation.
         expr.transform(|expr| {
-            if let Some(cast) = expr.as_any().downcast_ref::<CastExpr>() {
+            if let Some(cast) = expr.downcast_ref::<CastExpr>() {
                 let input_data_type =
                     cast.expr().data_type(&self.physical_file_schema)?;
                 let output_data_type = cast.target_field().data_type();
diff --git a/datafusion-examples/examples/data_io/json_shredding.rs b/datafusion-examples/examples/data_io/json_shredding.rs
index a6d328a396..72fbb56773 100644
--- a/datafusion-examples/examples/data_io/json_shredding.rs
+++ b/datafusion-examples/examples/data_io/json_shredding.rs
@@ -311,20 +311,16 @@ impl ShreddedJsonRewriter {
         expr: Arc<dyn PhysicalExpr>,
         physical_file_schema: &Schema,
     ) -> Result<Transformed<Arc<dyn PhysicalExpr>>> {
-        if let Some(func) = expr.as_any().downcast_ref::<ScalarFunctionExpr>()
+        if let Some(func) = expr.downcast_ref::<ScalarFunctionExpr>()
             && func.name() == "json_get_str"
             && func.args().len() == 2
         {
             // Get the key from the first argument
-            if let Some(literal) = func.args()[0]
-                .as_any()
-                .downcast_ref::<expressions::Literal>()
+            if let Some(literal) = func.args()[0].downcast_ref::<expressions::Literal>()
                 && let ScalarValue::Utf8(Some(field_name)) = literal.value()
             {
                 // Get the column from the second argument
-                if let Some(column) = func.args()[1]
-                    .as_any()
-                    .downcast_ref::<expressions::Column>()
+                if let Some(column) = func.args()[1].downcast_ref::<expressions::Column>()
                 {
                     let column_name = column.name();
                     // Check if there's a flat column with underscore prefix
diff --git a/datafusion-examples/examples/proto/expression_deduplication.rs b/datafusion-examples/examples/proto/expression_deduplication.rs
index 9f3940c82d..ec076b5249 100644
--- a/datafusion-examples/examples/proto/expression_deduplication.rs
+++ b/datafusion-examples/examples/proto/expression_deduplication.rs
@@ -129,7 +129,6 @@ pub async fn expression_deduplication() -> Result<()> {
     };
     let predicate = Arc::clone(filter_exec.predicate());
     let binary_expr = predicate
-        .as_any()
         .downcast_ref::<BinaryExpr>()
         .expect("Predicate is not a BinaryExpr");
     let left = &binary_expr.left();
diff --git a/datafusion/core/src/datasource/mod.rs b/datafusion/core/src/datasource/mod.rs
index 55bd7851ee..3fad65ae0d 100644
--- a/datafusion/core/src/datasource/mod.rs
+++ b/datafusion/core/src/datasource/mod.rs
@@ -167,7 +167,7 @@ mod tests {
     impl PhysicalExprAdapter for TestPhysicalExprAdapter {
         fn rewrite(&self, expr: Arc<dyn PhysicalExpr>) -> Result<Arc<dyn PhysicalExpr>> {
             expr.transform(|e| {
-                if let Some(column) = e.as_any().downcast_ref::<Column>() {
+                if let Some(column) = e.downcast_ref::<Column>() {
                     // If column is "extra_column" and missing from physical schema, inject "foo"
                     if column.name() == "extra_column"
                         && self.physical_file_schema.index_of("extra_column").is_err()
diff --git a/datafusion/core/tests/fuzz_cases/equivalence/utils.rs b/datafusion/core/tests/fuzz_cases/equivalence/utils.rs
index e8e4ea8b62..8350cafb21 100644
--- a/datafusion/core/tests/fuzz_cases/equivalence/utils.rs
+++ b/datafusion/core/tests/fuzz_cases/equivalence/utils.rs
@@ -49,7 +49,7 @@ pub fn output_schema(
         let data_type = source.data_type(input_schema)?;
         let nullable = source.nullable(input_schema)?;
         for (target, _) in targets.iter() {
-            let Some(column) = target.as_any().downcast_ref::<Column>() else {
+            let Some(column) = target.downcast_ref::<Column>() else {
                 return plan_err!("Expects to have column");
             };
             fields.push(Field::new(column.name(), data_type.clone(), nullable));
@@ -282,7 +282,7 @@ fn get_representative_arr(
     schema: SchemaRef,
 ) -> Option<ArrayRef> {
     for expr in eq_group.iter() {
-        let col = expr.as_any().downcast_ref::<Column>().unwrap();
+        let col = expr.downcast_ref::<Column>().unwrap();
         let (idx, _field) = schema.column_with_name(col.name()).unwrap();
         if let Some(res) = &existing_vec[idx] {
             return Some(Arc::clone(res));
@@ -370,7 +370,7 @@ pub fn generate_table_for_eq_properties(
 
     // Fill constant columns
     for constant in eq_properties.constants() {
-        let col = constant.expr.as_any().downcast_ref::<Column>().unwrap();
+        let col = constant.expr.downcast_ref::<Column>().unwrap();
         let (idx, _field) = schema.column_with_name(col.name()).unwrap();
         let arr =
             Arc::new(Float64Array::from_iter_values(vec![0 as f64; n_elem])) as ArrayRef;
@@ -382,7 +382,7 @@ pub fn generate_table_for_eq_properties(
         let (sort_columns, indices): (Vec<_>, Vec<_>) = ordering
             .iter()
             .map(|PhysicalSortExpr { expr, options }| {
-                let col = expr.as_any().downcast_ref::<Column>().unwrap();
+                let col = expr.downcast_ref::<Column>().unwrap();
                 let (idx, _field) = schema.column_with_name(col.name()).unwrap();
                 let arr = generate_random_array(n_elem, n_distinct);
                 (
@@ -408,7 +408,7 @@ pub fn generate_table_for_eq_properties(
                 .unwrap_or_else(|| generate_random_array(n_elem, n_distinct));
 
         for expr in eq_group.iter() {
-            let col = expr.as_any().downcast_ref::<Column>().unwrap();
+            let col = expr.downcast_ref::<Column>().unwrap();
             let (idx, _field) = schema.column_with_name(col.name()).unwrap();
             schema_vec[idx] = Some(Arc::clone(&representative_array));
         }
diff --git a/datafusion/core/tests/fuzz_cases/sort_preserving_repartition_fuzz.rs b/datafusion/core/tests/fuzz_cases/sort_preserving_repartition_fuzz.rs
index 8f3b8ea053..a1f38f161d 100644
--- a/datafusion/core/tests/fuzz_cases/sort_preserving_repartition_fuzz.rs
+++ b/datafusion/core/tests/fuzz_cases/sort_preserving_repartition_fuzz.rs
@@ -118,7 +118,7 @@ mod sp_repartition_fuzz_tests {
         schema: SchemaRef,
     ) -> Option<ArrayRef> {
         for expr in eq_group.iter() {
-            let col = expr.as_any().downcast_ref::<Column>().unwrap();
+            let col = expr.downcast_ref::<Column>().unwrap();
             let (idx, _field) = schema.column_with_name(col.name()).unwrap();
             if let Some(res) = &existing_vec[idx] {
                 return Some(res.clone());
@@ -149,7 +149,7 @@ mod sp_repartition_fuzz_tests {
 
         // Fill constant columns
         for constant in eq_properties.constants() {
-            let col = constant.expr.as_any().downcast_ref::<Column>().unwrap();
+            let col = constant.expr.downcast_ref::<Column>().unwrap();
             let (idx, _field) = schema.column_with_name(col.name()).unwrap();
             let arr =
                 Arc::new(UInt64Array::from_iter_values(vec![0; n_elem])) as ArrayRef;
@@ -161,7 +161,7 @@ mod sp_repartition_fuzz_tests {
             let (sort_columns, indices): (Vec<_>, Vec<_>) = ordering
                 .iter()
                 .map(|PhysicalSortExpr { expr, options }| {
-                    let col = expr.as_any().downcast_ref::<Column>().unwrap();
+                    let col = expr.downcast_ref::<Column>().unwrap();
                     let (idx, _field) = schema.column_with_name(col.name()).unwrap();
                     let arr = generate_random_array(n_elem, n_distinct);
                     (
@@ -187,7 +187,7 @@ mod sp_repartition_fuzz_tests {
                     .unwrap_or_else(|| generate_random_array(n_elem, n_distinct));
 
             for expr in eq_group.iter() {
-                let col = expr.as_any().downcast_ref::<Column>().unwrap();
+                let col = expr.downcast_ref::<Column>().unwrap();
                 let (idx, _field) = schema.column_with_name(col.name()).unwrap();
                 schema_vec[idx] = Some(representative_array.clone());
             }
diff --git a/datafusion/core/tests/parquet/expr_adapter.rs b/datafusion/core/tests/parquet/expr_adapter.rs
index cf32efbd70..fd70d74a91 100644
--- a/datafusion/core/tests/parquet/expr_adapter.rs
+++ b/datafusion/core/tests/parquet/expr_adapter.rs
@@ -490,7 +490,7 @@ impl PhysicalExprAdapter for CustomPhysicalExprAdapter {
     fn rewrite(&self, mut expr: Arc<dyn PhysicalExpr>) -> Result<Arc<dyn PhysicalExpr>> {
         expr = expr
             .transform(|expr| {
-                if let Some(column) = expr.as_any().downcast_ref::<Column>() {
+                if let Some(column) = expr.downcast_ref::<Column>() {
                     let field_name = column.name();
                     if self
                         .physical_file_schema
diff --git a/datafusion/core/tests/physical_optimizer/filter_pushdown.rs b/datafusion/core/tests/physical_optimizer/filter_pushdown.rs
index d058e44a85..4fa7b637ca 100644
--- a/datafusion/core/tests/physical_optimizer/filter_pushdown.rs
+++ b/datafusion/core/tests/physical_optimizer/filter_pushdown.rs
@@ -2959,7 +2959,7 @@ async fn test_discover_dynamic_filters_via_expressions_api() {
 
         // Check expressions from this node using apply_expressions
         let _ = plan.apply_expressions(&mut |expr| {
-            if let Some(_df) = expr.as_any().downcast_ref::<DynamicFilterPhysicalExpr>() {
+            if let Some(_df) = expr.downcast_ref::<DynamicFilterPhysicalExpr>() {
                 count += 1;
             }
             Ok(TreeNodeRecursion::Continue)
diff --git a/datafusion/core/tests/physical_optimizer/join_selection.rs b/datafusion/core/tests/physical_optimizer/join_selection.rs
index ee22e7b616..050baa9e79 100644
--- a/datafusion/core/tests/physical_optimizer/join_selection.rs
+++ b/datafusion/core/tests/physical_optimizer/join_selection.rs
@@ -759,7 +759,6 @@ async fn test_hash_join_swap_on_joins_with_projections(
 
 fn assert_col_expr(expr: &Arc<dyn PhysicalExpr>, name: &str, index: usize) {
     let col = expr
-        .as_any()
         .downcast_ref::<Column>()
         .expect("Projection items should be Column expression");
     assert_eq!(col.name(), name);
diff --git a/datafusion/core/tests/physical_optimizer/replace_with_order_preserving_variants.rs b/datafusion/core/tests/physical_optimizer/replace_with_order_preserving_variants.rs
index cdfed50116..601667ea02 100644
--- a/datafusion/core/tests/physical_optimizer/replace_with_order_preserving_variants.rs
+++ b/datafusion/core/tests/physical_optimizer/replace_with_order_preserving_variants.rs
@@ -1127,8 +1127,8 @@ fn hash_join_exec(
 ) -> Arc<dyn ExecutionPlan> {
     let left_on = col("c", &left.schema()).unwrap();
     let right_on = col("c", &right.schema()).unwrap();
-    let left_col = left_on.as_any().downcast_ref::<Column>().unwrap();
-    let right_col = right_on.as_any().downcast_ref::<Column>().unwrap();
+    let left_col = left_on.downcast_ref::<Column>().unwrap();
+    let right_col = right_on.downcast_ref::<Column>().unwrap();
     Arc::new(
         HashJoinExec::try_new(
             left,
diff --git a/datafusion/datasource-parquet/src/metadata.rs b/datafusion/datasource-parquet/src/metadata.rs
index 86c12b11c0..3d4d051abb 100644
--- a/datafusion/datasource-parquet/src/metadata.rs
+++ b/datafusion/datasource-parquet/src/metadata.rs
@@ -713,17 +713,13 @@ impl FileMetadata for CachedParquetMetaData {
 pub(crate) fn sort_expr_to_sorting_column(
     sort_expr: &PhysicalSortExpr,
 ) -> Result<SortingColumn> {
-    let column = sort_expr
-        .expr
-        .as_any()
-        .downcast_ref::<Column>()
-        .ok_or_else(|| {
-            DataFusionError::Plan(format!(
-                "Parquet sorting_columns only supports simple column references, \
+    let column = sort_expr.expr.downcast_ref::<Column>().ok_or_else(|| {
+        DataFusionError::Plan(format!(
+            "Parquet sorting_columns only supports simple column references, \
                  but got expression: {}",
-                sort_expr.expr
-            ))
-        })?;
+            sort_expr.expr
+        ))
+    })?;
 
     let column_idx: i32 = column.index().try_into().map_err(|_| {
         DataFusionError::Plan(format!(
diff --git a/datafusion/datasource-parquet/src/opener.rs b/datafusion/datasource-parquet/src/opener.rs
index 1db055880d..6d1c22c0b0 100644
--- a/datafusion/datasource-parquet/src/opener.rs
+++ b/datafusion/datasource-parquet/src/opener.rs
@@ -1916,8 +1916,8 @@ mod test {
             .try_map_exprs(|expr| replace_columns_with_literals(expr, &constants))
             .unwrap();
         let exprs = rewritten.as_ref();
-        assert!(exprs[0].expr.as_any().downcast_ref::<Literal>().is_some());
-        assert!(exprs[1].expr.as_any().downcast_ref::<Column>().is_some());
+        assert!(exprs[0].expr.downcast_ref::<Literal>().is_some());
+        assert!(exprs[1].expr.downcast_ref::<Column>().is_some());
 
         // Only column `b` should remain in the projection mask
         assert_eq!(rewritten.column_indices(), vec![1]);
@@ -1930,7 +1930,7 @@ mod test {
         let expr: Arc<dyn PhysicalExpr> = Arc::new(Column::new("a", 0));
 
         let rewritten = replace_columns_with_literals(expr, &constants).unwrap();
-        assert!(rewritten.as_any().downcast_ref::<Literal>().is_some());
+        assert!(rewritten.downcast_ref::<Literal>().is_some());
     }
 
     async fn count_batches_and_rows(
diff --git a/datafusion/datasource-parquet/src/row_filter.rs b/datafusion/datasource-parquet/src/row_filter.rs
index 67b65321d9..c5c3720558 100644
--- a/datafusion/datasource-parquet/src/row_filter.rs
+++ b/datafusion/datasource-parquet/src/row_filter.rs
@@ -420,10 +420,7 @@ impl TreeNodeVisitor<'_> for PushdownChecker<'_> {
         {
             let args = func.args();
 
-            if let Some(column) = args
-                .first()
-                .and_then(|a| a.as_any().downcast_ref::<Column>())
-            {
+            if let Some(column) = args.first().and_then(|a| a.downcast_ref::<Column>()) {
                 // for Map columns, get_field performs a runtime key lookup rather than a
                 // schema-level field access so the entire Map column must be read,
                 // we skip the struct field optimization and defer to normal Column traversal
@@ -451,7 +448,7 @@ impl TreeNodeVisitor<'_> for PushdownChecker<'_> {
                     let field_path = args[1..]
                         .iter()
                         .map(|arg| {
-                            arg.as_any().downcast_ref::<Literal>().and_then(|lit| {
+                            arg.downcast_ref::<Literal>().and_then(|lit| {
                                 lit.value().try_as_str().flatten().map(|s| s.to_string())
                             })
                         })
@@ -481,7 +478,7 @@ impl TreeNodeVisitor<'_> for PushdownChecker<'_> {
             }
         }
 
-        if let Some(column) = node.as_any().downcast_ref::<Column>()
+        if let Some(column) = node.downcast_ref::<Column>()
             && let Some(recursion) = self.check_single_column(column.name())
         {
             return Ok(recursion);
@@ -611,14 +608,12 @@ pub(crate) fn build_projection_read_plan(
     // fast path: if every expression is a plain Column reference, skip all
     // struct analysis and use root-level projection directly
     let exprs = exprs.into_iter().collect::<Vec<_>>();
-    let all_plain_columns = exprs
-        .iter()
-        .all(|e| e.as_any().downcast_ref::<Column>().is_some());
+    let all_plain_columns = exprs.iter().all(|e| e.downcast_ref::<Column>().is_some());
 
     if all_plain_columns {
         let mut root_indices: Vec<usize> = exprs
             .iter()
-            .map(|e| e.as_any().downcast_ref::<Column>().unwrap().index())
+            .map(|e| e.downcast_ref::<Column>().unwrap().index())
             .collect();
         root_indices.sort_unstable();
         root_indices.dedup();
diff --git a/datafusion/datasource-parquet/src/supported_predicates.rs b/datafusion/datasource-parquet/src/supported_predicates.rs
index a205c12dd0..5c6b5f3ec9 100644
--- a/datafusion/datasource-parquet/src/supported_predicates.rs
+++ b/datafusion/datasource-parquet/src/supported_predicates.rs
@@ -70,8 +70,8 @@ impl SupportsListPushdown for dyn PhysicalExpr {
 ///
 /// These checks are universally supported for all column types.
 fn is_null_check(expr: &dyn PhysicalExpr) -> bool {
-    expr.as_any().downcast_ref::<IsNullExpr>().is_some()
-        || expr.as_any().downcast_ref::<IsNotNullExpr>().is_some()
+    expr.downcast_ref::<IsNullExpr>().is_some()
+        || expr.downcast_ref::<IsNotNullExpr>().is_some()
 }
 
 /// Checks if an expression is a scalar function registered for list pushdown.
@@ -86,8 +86,7 @@ fn is_supported_scalar_function(expr: &dyn PhysicalExpr) -> bool {
 }
 
 fn scalar_function_name(expr: &dyn PhysicalExpr) -> Option<&str> {
-    expr.as_any()
-        .downcast_ref::<ScalarFunctionExpr>()
+    expr.downcast_ref::<ScalarFunctionExpr>()
         .map(ScalarFunctionExpr::name)
 }
 
diff --git a/datafusion/datasource/src/file_scan_config/mod.rs b/datafusion/datasource/src/file_scan_config/mod.rs
index f4e4e0a4de..c1afd35da3 100644
--- a/datafusion/datasource/src/file_scan_config/mod.rs
+++ b/datafusion/datasource/src/file_scan_config/mod.rs
@@ -620,7 +620,7 @@ impl DataSource for FileScanConfig {
                             .iter()
                             .map(|proj_expr| {
                                 if let Some(column) =
-                                    proj_expr.expr.as_any().downcast_ref::<Column>()
+                                    proj_expr.expr.downcast_ref::<Column>()
                                 {
                                     if column.name() == proj_expr.alias {
                                         column.name().to_string()
@@ -1075,7 +1075,7 @@ impl FileScanConfig {
             // (e.g. due to unnecessary projections being removed)
             reassign_expr_columns(Arc::clone(expr), schema)
                 .ok()
-                .and_then(|expr| match expr.as_any().downcast_ref::<BinaryExpr>() {
+                .and_then(|expr| match expr.downcast_ref::<BinaryExpr>() {
                     Some(expr) if expr.op() == &Operator::Eq => {
                         Some((Arc::clone(expr.left()), Arc::clone(expr.right())))
                     }
@@ -1918,7 +1918,7 @@ mod tests {
 
         for class in eq_group.iter() {
             for expr in class.iter() {
-                if let Some(col) = expr.as_any().downcast_ref::<Column>() {
+                if let Some(col) = expr.downcast_ref::<Column>() {
                     assert_ne!(
                         col.name(),
                         "c2",
@@ -2350,10 +2350,7 @@ mod tests {
             Partitioning::Hash(exprs, num_partitions) => {
                 assert_eq!(num_partitions, 3);
                 assert_eq!(exprs.len(), 1);
-                assert_eq!(
-                    exprs[0].as_any().downcast_ref::<Column>().unwrap().name(),
-                    "date"
-                );
+                assert_eq!(exprs[0].downcast_ref::<Column>().unwrap().name(), "date");
             }
             _ => panic!("Expected Hash partitioning"),
         }
@@ -2383,7 +2380,7 @@ mod tests {
                 assert_eq!(exprs.len(), 2);
                 let col_names: Vec<_> = exprs
                     .iter()
-                    .map(|e| e.as_any().downcast_ref::<Column>().unwrap().name())
+                    .map(|e| e.downcast_ref::<Column>().unwrap().name())
                     .collect();
                 assert_eq!(col_names, vec!["year", "month"]);
             }
diff --git a/datafusion/datasource/src/file_scan_config/sort_pushdown.rs b/datafusion/datasource/src/file_scan_config/sort_pushdown.rs
index db4019071f..af08ed71b9 100644
--- a/datafusion/datasource/src/file_scan_config/sort_pushdown.rs
+++ b/datafusion/datasource/src/file_scan_config/sort_pushdown.rs
@@ -389,7 +389,7 @@ pub(crate) fn ordered_column_indices_from_projection(
     projection
         .expr_iter()
         .map(|e| {
-            let index = e.as_any().downcast_ref::<Column>()?.index();
+            let index = e.downcast_ref::<Column>()?.index();
             Some(index)
         })
         .collect::<Option<Vec<usize>>>()
@@ -402,7 +402,7 @@ fn sort_columns_from_physical_sort_exprs_nullable(
 ) -> Option<Vec<Column>> {
     order
         .iter()
-        .map(|expr| expr.expr.as_any().downcast_ref::<Column>().cloned())
+        .map(|expr| expr.expr.downcast_ref::<Column>().cloned())
         .collect()
 }
 
diff --git a/datafusion/datasource/src/projection.rs b/datafusion/datasource/src/projection.rs
index 9a0cb494e4..ac33a96ca8 100644
--- a/datafusion/datasource/src/projection.rs
+++ b/datafusion/datasource/src/projection.rs
@@ -122,7 +122,7 @@ fn inject_partition_columns_into_projection(
             let expr = Arc::clone(&projection.expr)
                 .transform(|expr| {
                     let original_expr = Arc::clone(&expr);
-                    if let Some(column) = expr.as_any().downcast_ref::<Column>() {
+                    if let Some(column) = expr.downcast_ref::<Column>() {
                         // Check if this column index corresponds to a partition column
                         if let Some(pci) = partition_columns
                             .iter()
@@ -199,7 +199,7 @@ impl SplitProjection {
             proj_expr
                 .expr
                 .apply(|expr| {
-                    if let Some(column) = expr.as_any().downcast_ref::<Column>() {
+                    if let Some(column) = expr.downcast_ref::<Column>() {
                         all_columns
                             .entry(column.index())
                             .or_insert_with(|| column.name().to_string());
@@ -250,7 +250,7 @@ impl SplitProjection {
                 let expr = Arc::clone(&proj_expr.expr)
                     .transform(|expr| {
                         let original_expr = Arc::clone(&expr);
-                        if let Some(column) = expr.as_any().downcast_ref::<Column>()
+                        if let Some(column) = expr.downcast_ref::<Column>()
                             && let Some(new_column) = column_mapping.get(&column.index())
                         {
                             return Ok(Transformed::yes(Arc::clone(new_column)));
diff --git a/datafusion/datasource/src/statistics.rs b/datafusion/datasource/src/statistics.rs
index 5835e47bba..e7fef49539 100644
--- a/datafusion/datasource/src/statistics.rs
+++ b/datafusion/datasource/src/statistics.rs
@@ -280,7 +280,7 @@ fn sort_columns_from_physical_sort_exprs(
 ) -> Option<Vec<&Column>> {
     sort_order
         .iter()
-        .map(|expr| expr.expr.as_any().downcast_ref::<Column>())
+        .map(|expr| expr.expr.downcast_ref::<Column>())
         .collect()
 }
 
diff --git a/datafusion/execution/src/cache/cache_unit.rs b/datafusion/execution/src/cache/cache_unit.rs
index 59bb3f0702..49e16ca4b6 100644
--- a/datafusion/execution/src/cache/cache_unit.rs
+++ b/datafusion/execution/src/cache/cache_unit.rs
@@ -185,10 +185,6 @@ mod tests {
     }
 
     impl PhysicalExpr for MockExpr {
-        fn as_any(&self) -> &dyn std::any::Any {
-            self
-        }
-
         fn data_type(
             &self,
             _input_schema: &Schema,
diff --git a/datafusion/ffi/src/physical_expr/mod.rs b/datafusion/ffi/src/physical_expr/mod.rs
index 189a1e4782..a82adffd85 100644
--- a/datafusion/ffi/src/physical_expr/mod.rs
+++ b/datafusion/ffi/src/physical_expr/mod.rs
@@ -18,7 +18,6 @@
 pub(crate) mod partitioning;
 pub(crate) mod sort;
 
-use std::any::Any;
 use std::ffi::c_void;
 use std::fmt::{Display, Formatter};
 use std::hash::{DefaultHasher, Hash, Hasher};
@@ -448,7 +447,7 @@ impl Drop for FFI_PhysicalExpr {
 impl From<Arc<dyn PhysicalExpr>> for FFI_PhysicalExpr {
     /// Creates a new [`FFI_PhysicalExpr`].
     fn from(expr: Arc<dyn PhysicalExpr>) -> Self {
-        if let Some(expr) = expr.as_any().downcast_ref::<ForeignPhysicalExpr>() {
+        if let Some(expr) = expr.downcast_ref::<ForeignPhysicalExpr>() {
             return expr.expr.clone();
         }
 
@@ -522,10 +521,6 @@ impl Clone for FFI_PhysicalExpr {
 }
 
 impl PhysicalExpr for ForeignPhysicalExpr {
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     fn data_type(&self, input_schema: &Schema) -> Result<DataType> {
         unsafe {
             let schema = WrappedSchema::from(Arc::new(input_schema.clone()));
diff --git a/datafusion/functions-aggregate/src/count.rs b/datafusion/functions-aggregate/src/count.rs
index 81b6eda3f9..0edfc152ff 100644
--- a/datafusion/functions-aggregate/src/count.rs
+++ b/datafusion/functions-aggregate/src/count.rs
@@ -369,7 +369,7 @@ impl AggregateUDFImpl for Count {
         if statistics_args.is_distinct {
             // Only column references can be resolved from statistics;
             // expressions like casts or literals are not supported.
-            let col_expr = expr.as_any().downcast_ref::<expressions::Column>()?;
+            let col_expr = expr.downcast_ref::<expressions::Column>()?;
             if let Precision::Exact(dc) = col_stats[col_expr.index()].distinct_count {
                 let dc = i64::try_from(dc).ok()?;
                 return Some(ScalarValue::Int64(Some(dc)));
@@ -382,13 +382,12 @@ impl AggregateUDFImpl for Count {
         };
 
         // TODO optimize with exprs other than Column
-        if let Some(col_expr) = expr.as_any().downcast_ref::<expressions::Column>() {
+        if let Some(col_expr) = expr.downcast_ref::<expressions::Column>() {
             if let Precision::Exact(val) = col_stats[col_expr.index()].null_count {
                 let count = i64::try_from(num_rows - val).ok()?;
                 return Some(ScalarValue::Int64(Some(count)));
             }
-        } else if let Some(lit_expr) =
-            expr.as_any().downcast_ref::<expressions::Literal>()
+        } else if let Some(lit_expr) = expr.downcast_ref::<expressions::Literal>()
             && lit_expr.value() == &COUNT_STAR_EXPANSION
         {
             let num_rows = i64::try_from(num_rows).ok()?;
diff --git a/datafusion/functions-aggregate/src/min_max.rs b/datafusion/functions-aggregate/src/min_max.rs
index 9d05c57b02..939ee2a2d8 100644
--- a/datafusion/functions-aggregate/src/min_max.rs
+++ b/datafusion/functions-aggregate/src/min_max.rs
@@ -171,9 +171,8 @@ trait FromColumnStatistics {
                     let col_stats = &statistics_args.statistics.column_statistics;
                     if statistics_args.exprs.len() == 1 {
                         // TODO optimize with exprs other than Column
-                        if let Some(col_expr) = statistics_args.exprs[0]
-                            .as_any()
-                            .downcast_ref::<expressions::Column>()
+                        if let Some(col_expr) =
+                            statistics_args.exprs[0].downcast_ref::<expressions::Column>()
                         {
                             return self.value_from_column_statistics(
                                 &col_stats[col_expr.index()],
diff --git a/datafusion/functions-aggregate/src/nth_value.rs b/datafusion/functions-aggregate/src/nth_value.rs
index 0d76451955..d131bb7a84 100644
--- a/datafusion/functions-aggregate/src/nth_value.rs
+++ b/datafusion/functions-aggregate/src/nth_value.rs
@@ -125,7 +125,6 @@ impl AggregateUDFImpl for NthValueAgg {
 
     fn accumulator(&self, acc_args: AccumulatorArgs) -> Result<Box<dyn Accumulator>> {
         let n = match acc_args.exprs[1]
-            .as_any()
             .downcast_ref::<Literal>()
             .map(|lit| lit.value())
         {
diff --git a/datafusion/functions-aggregate/src/string_agg.rs b/datafusion/functions-aggregate/src/string_agg.rs
index e62b26da99..f0757818af 100644
--- a/datafusion/functions-aggregate/src/string_agg.rs
+++ b/datafusion/functions-aggregate/src/string_agg.rs
@@ -120,7 +120,7 @@ impl StringAgg {
 
     /// Extract the delimiter string from the second argument expression.
     fn extract_delimiter(args: &AccumulatorArgs) -> Result<String> {
-        let Some(lit) = args.exprs[1].as_any().downcast_ref::<Literal>() else {
+        let Some(lit) = args.exprs[1].downcast_ref::<Literal>() else {
             return not_impl_err!("string_agg delimiter must be a string literal");
         };
 
diff --git a/datafusion/functions-window/src/lead_lag.rs b/datafusion/functions-window/src/lead_lag.rs
index 604f1d4a0e..b78709b22c 100644
--- a/datafusion/functions-window/src/lead_lag.rs
+++ b/datafusion/functions-window/src/lead_lag.rs
@@ -314,8 +314,7 @@ impl WindowUDFImpl for WindowShift {
         }
         match args {
             [_, expr, ..] => {
-                let Some(lit) = expr.as_any().downcast_ref::<expressions::Literal>()
-                else {
+                let Some(lit) = expr.downcast_ref::<expressions::Literal>() else {
                     return LimitEffect::Unknown;
                 };
                 let ScalarValue::Int64(Some(amount)) = lit.value() else {
diff --git a/datafusion/functions-window/src/utils.rs b/datafusion/functions-window/src/utils.rs
index 4d0c4d181a..95b60864cc 100644
--- a/datafusion/functions-window/src/utils.rs
+++ b/datafusion/functions-window/src/utils.rs
@@ -39,7 +39,6 @@ pub(crate) fn get_scalar_value_from_args(
 ) -> Result<Option<ScalarValue>> {
     Ok(if let Some(field) = args.get(index) {
         let tmp = field
-            .as_any()
             .downcast_ref::<Literal>()
             .ok_or_else(|| DataFusionError::NotImplemented(
                 format!("There is only support Literal types for field at idx: {index} in Window Function"),
diff --git a/datafusion/physical-expr-adapter/src/schema_rewriter.rs b/datafusion/physical-expr-adapter/src/schema_rewriter.rs
index 5805f933a9..9fb4950317 100644
--- a/datafusion/physical-expr-adapter/src/schema_rewriter.rs
+++ b/datafusion/physical-expr-adapter/src/schema_rewriter.rs
@@ -69,7 +69,7 @@ where
     V: Borrow<ScalarValue>,
 {
     expr.transform_down(|expr| {
-        if let Some(column) = expr.as_any().downcast_ref::<Column>()
+        if let Some(column) = expr.downcast_ref::<Column>()
             && let Some(replacement_value) = replacements.get(column.name())
         {
             return Ok(Transformed::yes(expressions::lit(
@@ -117,7 +117,7 @@ where
 /// impl PhysicalExprAdapter for CustomPhysicalExprAdapter {
 ///     fn rewrite(&self, expr: Arc<dyn PhysicalExpr>) -> Result<Arc<dyn PhysicalExpr>> {
 ///         expr.transform(|expr| {
-///             if let Some(column) = expr.as_any().downcast_ref::<Column>() {
+///             if let Some(column) = expr.downcast_ref::<Column>() {
 ///                 // Check if the column exists in the physical schema
 ///                 if self.physical_file_schema.index_of(column.name()).is_err() {
 ///                     // If the column is missing, fill it with a default value instead of null
@@ -281,7 +281,7 @@ impl DefaultPhysicalExprAdapterRewriter {
             return Ok(Transformed::yes(transformed));
         }
 
-        if let Some(column) = expr.as_any().downcast_ref::<Column>() {
+        if let Some(column) = expr.downcast_ref::<Column>() {
             return self.rewrite_column(Arc::clone(&expr), column);
         }
 
@@ -311,10 +311,7 @@ impl DefaultPhysicalExprAdapterRewriter {
             None => return Ok(None),
         };
 
-        let lit = match field_name_expr
-            .as_any()
-            .downcast_ref::<expressions::Literal>()
-        {
+        let lit = match field_name_expr.downcast_ref::<expressions::Literal>() {
             Some(lit) => lit,
             None => return Ok(None),
         };
@@ -324,7 +321,7 @@ impl DefaultPhysicalExprAdapterRewriter {
             None => return Ok(None),
         };
 
-        let column = match source_expr.as_any().downcast_ref::<Column>() {
+        let column = match source_expr.downcast_ref::<Column>() {
             Some(column) => column,
             None => return Ok(None),
         };
@@ -639,15 +636,12 @@ mod tests {
     use datafusion_physical_expr::expressions::{Column, Literal, col};
 
     fn assert_cast_expr(expr: &Arc<dyn PhysicalExpr>) -> &CastExpr {
-        expr.as_any()
-            .downcast_ref::<CastExpr>()
-            .expect("Expected CastExpr")
+        expr.downcast_ref::<CastExpr>().expect("Expected CastExpr")
     }
 
     fn assert_cast_input_column(cast_expr: &CastExpr, name: &str, index: usize) {
         let inner_col = cast_expr
             .expr()
-            .as_any()
             .downcast_ref::<Column>()
             .expect("Expected inner Column");
         assert_eq!(inner_col.name(), name);
@@ -696,7 +690,7 @@ mod tests {
         let result = adapter.rewrite(column_expr).unwrap();
 
         // Should be wrapped in a cast expression
-        assert!(result.as_any().downcast_ref::<CastExpr>().is_some());
+        assert!(result.downcast_ref::<CastExpr>().is_some());
     }
 
     #[test]
@@ -757,14 +751,12 @@ mod tests {
 
         let result = adapter.rewrite(Arc::new(expr)).unwrap();
         let outer = result
-            .as_any()
             .downcast_ref::<expressions::BinaryExpr>()
             .expect("Expected outer BinaryExpr");
         assert_eq!(*outer.op(), Operator::Or);
 
         let left = outer
             .left()
-            .as_any()
             .downcast_ref::<expressions::BinaryExpr>()
             .expect("Expected left BinaryExpr");
         assert_eq!(*left.op(), Operator::Plus);
@@ -775,13 +767,11 @@ mod tests {
 
         let right = outer
             .right()
-            .as_any()
             .downcast_ref::<expressions::BinaryExpr>()
             .expect("Expected right BinaryExpr");
         assert_eq!(*right.op(), Operator::Gt);
         let null_literal = right
             .left()
-            .as_any()
             .downcast_ref::<Literal>()
             .expect("Expected null literal");
         assert_eq!(*null_literal.value(), ScalarValue::Float64(None));
@@ -882,7 +872,7 @@ mod tests {
         let result = adapter.rewrite(column_expr)?;
 
         // Should be replaced with a literal null
-        if let Some(literal) = result.as_any().downcast_ref::<Literal>() {
+        if let Some(literal) = result.downcast_ref::<Literal>() {
             assert_eq!(*literal.value(), ScalarValue::Float64(None));
         } else {
             panic!("Expected literal expression");
@@ -909,7 +899,6 @@ mod tests {
 
         let result = adapter.rewrite(Arc::new(Column::new("b", 1)))?;
         let literal = result
-            .as_any()
             .downcast_ref::<Literal>()
             .expect("Expected literal expression");
 
@@ -975,7 +964,6 @@ mod tests {
 
         // Should be replaced with the partition value
         let literal = result
-            .as_any()
             .downcast_ref::<Literal>()
             .expect("Expected literal expression");
         assert_eq!(*literal.value(), partition_value);
@@ -992,7 +980,7 @@ mod tests {
             Arc::new(Column::new("partition_col", 0)) as Arc<dyn PhysicalExpr>;
         let result = replace_columns_with_literals(column_expr, &replacements)?;
 
-        assert!(result.as_any().downcast_ref::<Column>().is_some());
+        assert!(result.downcast_ref::<Column>().is_some());
         Ok(())
     }
 
diff --git a/datafusion/physical-expr-common/src/physical_expr.rs b/datafusion/physical-expr-common/src/physical_expr.rs
index 7107b0a900..a42a1560cb 100644
--- a/datafusion/physical-expr-common/src/physical_expr.rs
+++ b/datafusion/physical-expr-common/src/physical_expr.rs
@@ -72,9 +72,6 @@ pub type PhysicalExprRef = Arc<dyn PhysicalExpr>;
 /// [`create_physical_expr`]: https://docs.rs/datafusion/latest/datafusion/physical_expr/fn.create_physical_expr.html
 /// [`Column`]: https://docs.rs/datafusion/latest/datafusion/physical_expr/expressions/struct.Column.html
 pub trait PhysicalExpr: Any + Send + Sync + Display + Debug + DynEq + DynHash {
-    /// Returns the physical expression as [`Any`] so that it can be
-    /// downcast to a specific implementation.
-    fn as_any(&self) -> &dyn Any;
     /// Get the data type of this expression, given the schema of the input
     fn data_type(&self, input_schema: &Schema) -> Result<DataType> {
         Ok(self.return_field(input_schema)?.data_type().to_owned())
@@ -449,9 +446,29 @@ pub trait PhysicalExpr: Any + Send + Sync + Display + Debug + DynEq + DynHash {
 )]
 pub use datafusion_expr_common::dyn_eq::{DynEq, DynHash};
 
+impl dyn PhysicalExpr {
+    /// Returns `true` if the expression is of type `T`.
+    ///
+    /// Prefer this over `downcast_ref::<T>().is_some()`. Works correctly when
+    /// called on `Arc<dyn PhysicalExpr>` via auto-deref.
+    pub fn is<T: PhysicalExpr>(&self) -> bool {
+        (self as &dyn Any).is::<T>()
+    }
+
+    /// Attempts to downcast this expression to a concrete type `T`, returning
+    /// `None` if the expression is not of that type.
+    ///
+    /// Works correctly when called on `Arc<dyn PhysicalExpr>` via auto-deref,
+    /// unlike `(&arc as &dyn Any).downcast_ref::<T>()` which would attempt to
+    /// downcast the `Arc` itself.
+    pub fn downcast_ref<T: PhysicalExpr>(&self) -> Option<&T> {
+        (self as &dyn Any).downcast_ref()
+    }
+}
+
 impl PartialEq for dyn PhysicalExpr {
     fn eq(&self, other: &Self) -> bool {
-        self.dyn_eq(other.as_any())
+        self.dyn_eq(other as &dyn Any)
     }
 }
 impl Eq for dyn PhysicalExpr {}
@@ -528,7 +545,6 @@ where
 /// # Example
 /// ```
 /// # // The boilerplate needed to create a `PhysicalExpr` for the example
-/// # use std::any::Any;
 /// use std::collections::HashMap;
 /// # use std::fmt::Formatter;
 /// # use std::sync::Arc;
@@ -539,7 +555,7 @@ where
 /// # use datafusion_physical_expr_common::physical_expr::{fmt_sql, DynEq, PhysicalExpr};
 /// # #[derive(Debug, PartialEq, Eq, Hash)]
 /// # struct MyExpr {}
-/// # impl PhysicalExpr for MyExpr {fn as_any(&self) -> &dyn Any { unimplemented!() }
+/// # impl PhysicalExpr for MyExpr {
 /// # fn data_type(&self, input_schema: &Schema) -> Result<DataType> { unimplemented!() }
 /// # fn nullable(&self, input_schema: &Schema) -> Result<bool> { unimplemented!() }
 /// # fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> { unimplemented!() }
@@ -684,10 +700,6 @@ mod test {
     struct TestExpr {}
 
     impl PhysicalExpr for TestExpr {
-        fn as_any(&self) -> &dyn std::any::Any {
-            self
-        }
-
         fn data_type(&self, _schema: &Schema) -> datafusion_common::Result<DataType> {
             Ok(DataType::Int64)
         }
diff --git a/datafusion/physical-expr-common/src/sort_expr.rs b/datafusion/physical-expr-common/src/sort_expr.rs
index fa961981c0..84ffb92eaa 100644
--- a/datafusion/physical-expr-common/src/sort_expr.rs
+++ b/datafusion/physical-expr-common/src/sort_expr.rs
@@ -53,7 +53,6 @@ use indexmap::IndexSet;
 /// # #[derive(Clone, Debug, PartialEq, Eq, Hash)]
 /// # struct MyPhysicalExpr;
 /// # impl PhysicalExpr for MyPhysicalExpr {
-/// #  fn as_any(&self) -> &dyn Any {todo!() }
 /// #  fn data_type(&self, input_schema: &Schema) -> Result<DataType> {todo!()}
 /// #  fn nullable(&self, input_schema: &Schema) -> Result<bool> {todo!() }
 /// #  fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> {todo!() }
diff --git a/datafusion/physical-expr/src/analysis.rs b/datafusion/physical-expr/src/analysis.rs
index ad54d382c9..1dca36b75f 100644
--- a/datafusion/physical-expr/src/analysis.rs
+++ b/datafusion/physical-expr/src/analysis.rs
@@ -202,7 +202,7 @@ pub fn analyze(
         let target_expr_and_indices = graph.gather_node_indices(columns.as_slice());
 
         for (expr, index) in &target_expr_and_indices {
-            if let Some(column) = expr.as_any().downcast_ref::<Column>()
+            if let Some(column) = expr.downcast_ref::<Column>()
                 && let Some(bound) =
                     target_boundaries.iter().find(|b| b.column == *column)
             {
@@ -241,7 +241,7 @@ fn shrink_boundaries(
 ) -> Result<AnalysisContext> {
     let initial_boundaries = target_boundaries.clone();
     target_expr_and_indices.iter().for_each(|(expr, i)| {
-        if let Some(column) = expr.as_any().downcast_ref::<Column>()
+        if let Some(column) = expr.downcast_ref::<Column>()
             && let Some(bound) = target_boundaries
                 .iter_mut()
                 .find(|bound| bound.column.eq(column))
diff --git a/datafusion/physical-expr/src/async_scalar_function.rs b/datafusion/physical-expr/src/async_scalar_function.rs
index 9f58d7c77a..5612e63b53 100644
--- a/datafusion/physical-expr/src/async_scalar_function.rs
+++ b/datafusion/physical-expr/src/async_scalar_function.rs
@@ -26,7 +26,6 @@ use datafusion_expr::ScalarFunctionArgs;
 use datafusion_expr::async_udf::AsyncScalarUDF;
 use datafusion_expr_common::columnar_value::ColumnarValue;
 use datafusion_physical_expr_common::physical_expr::PhysicalExpr;
-use std::any::Any;
 use std::fmt::Display;
 use std::hash::{Hash, Hasher};
 use std::sync::Arc;
@@ -68,7 +67,7 @@ impl AsyncFuncExpr {
         func: Arc<dyn PhysicalExpr>,
         schema: &Schema,
     ) -> Result<Self> {
-        let Some(_) = func.as_any().downcast_ref::<ScalarFunctionExpr>() else {
+        let Some(_) = func.downcast_ref::<ScalarFunctionExpr>() else {
             return internal_err!(
                 "unexpected function type, expected ScalarFunctionExpr, got: {:?}",
                 func
@@ -99,7 +98,7 @@ impl AsyncFuncExpr {
 
     /// Return the ideal batch size for this function
     pub fn ideal_batch_size(&self) -> Result<Option<usize>> {
-        if let Some(expr) = self.func.as_any().downcast_ref::<ScalarFunctionExpr>()
+        if let Some(expr) = self.func.downcast_ref::<ScalarFunctionExpr>()
             && let Some(udf) = expr.fun().inner().downcast_ref::<AsyncScalarUDF>()
         {
             return Ok(udf.ideal_batch_size());
@@ -115,8 +114,7 @@ impl AsyncFuncExpr {
         batch: &RecordBatch,
         config_options: Arc<ConfigOptions>,
     ) -> Result<ColumnarValue> {
-        let Some(scalar_function_expr) =
-            self.func.as_any().downcast_ref::<ScalarFunctionExpr>()
+        let Some(scalar_function_expr) = self.func.downcast_ref::<ScalarFunctionExpr>()
         else {
             return internal_err!(
                 "unexpected function type, expected ScalarFunctionExpr, got: {:?}",
@@ -209,10 +207,6 @@ impl AsyncFuncExpr {
 }
 
 impl PhysicalExpr for AsyncFuncExpr {
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     fn data_type(&self, input_schema: &Schema) -> Result<DataType> {
         self.func.data_type(input_schema)
     }
diff --git a/datafusion/physical-expr/src/equivalence/class.rs b/datafusion/physical-expr/src/equivalence/class.rs
index 2a4f88e50a..d00a4a3227 100644
--- a/datafusion/physical-expr/src/equivalence/class.rs
+++ b/datafusion/physical-expr/src/equivalence/class.rs
@@ -15,6 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use std::any::Any;
 use std::fmt::Display;
 use std::ops::Deref;
 use std::sync::Arc;
@@ -153,7 +154,7 @@ impl From<Arc<dyn PhysicalExpr>> for ConstExpr {
         // By default, assume constant expressions are not same across partitions.
         // However, if we have a literal, it will have a single value that is the
         // same across all partitions.
-        let across = if let Some(lit) = expr.as_any().downcast_ref::<Literal>() {
+        let across = if let Some(lit) = expr.downcast_ref::<Literal>() {
             AcrossPartitions::Uniform(Some(lit.value().clone()))
         } else {
             AcrossPartitions::Heterogeneous
@@ -201,7 +202,7 @@ impl EquivalenceClass {
     /// Insert the expression into this class, meaning it is known to be equal to
     /// all other expressions in this class.
     pub fn push(&mut self, expr: Arc<dyn PhysicalExpr>) {
-        if let Some(lit) = expr.as_any().downcast_ref::<Literal>() {
+        if let Some(lit) = expr.downcast_ref::<Literal>() {
             let expr_across = AcrossPartitions::Uniform(Some(lit.value().clone()));
             if let Some(across) = self.constant.as_mut() {
                 // TODO: Return an error if constant values do not agree.
@@ -591,7 +592,7 @@ impl EquivalenceGroup {
         expr: &Arc<dyn PhysicalExpr>,
     ) -> Option<Arc<dyn PhysicalExpr>> {
         // Literals don't need to be projected
-        if expr.as_any().downcast_ref::<Literal>().is_some() {
+        if expr.downcast_ref::<Literal>().is_some() {
             return Some(Arc::clone(expr));
         }
 
@@ -734,7 +735,7 @@ impl EquivalenceGroup {
         &self,
         expr: &Arc<dyn PhysicalExpr>,
     ) -> Option<AcrossPartitions> {
-        if let Some(lit) = expr.as_any().downcast_ref::<Literal>() {
+        if let Some(lit) = expr.downcast_ref::<Literal>() {
             return Some(AcrossPartitions::Uniform(Some(lit.value().clone())));
         }
         if let Some(cls) = self.get_equivalence_class(expr)
@@ -841,7 +842,7 @@ impl EquivalenceGroup {
         }
 
         // Type equality check through reflection
-        if left.as_any().type_id() != right.as_any().type_id() {
+        if (left as &dyn Any).type_id() != (right as &dyn Any).type_id() {
             return false;
         }
 
diff --git a/datafusion/physical-expr/src/equivalence/properties/mod.rs b/datafusion/physical-expr/src/equivalence/properties/mod.rs
index 214e3ca6b2..bb74cd1d9c 100644
--- a/datafusion/physical-expr/src/equivalence/properties/mod.rs
+++ b/datafusion/physical-expr/src/equivalence/properties/mod.rs
@@ -209,7 +209,7 @@ impl EquivalenceProperties {
         sort_expr: &PhysicalSortExpr,
         expr_type: &DataType,
     ) -> Option<PhysicalSortExpr> {
-        let cast_expr = r_expr.as_any().downcast_ref::<CastExpr>()?;
+        let cast_expr = r_expr.downcast_ref::<CastExpr>()?;
 
         (cast_expr.expr().eq(&sort_expr.expr)
             && CastExpr::check_bigger_cast(cast_expr.cast_type(), expr_type))
@@ -738,8 +738,7 @@ impl EquivalenceProperties {
                         // Build a map of column positions in the ordering:
                         let mut col_positions = HashMap::with_capacity(length);
                         for (pos, req) in ordering.iter().enumerate() {
-                            if let Some(col) = req.expr.as_any().downcast_ref::<Column>()
-                            {
+                            if let Some(col) = req.expr.downcast_ref::<Column>() {
                                 let nullable = col.nullable(&self.schema).unwrap_or(true);
                                 col_positions.insert(col.index(), (pos, nullable));
                             }
@@ -783,8 +782,7 @@ impl EquivalenceProperties {
                         // Build a map of column positions in the ordering:
                         let mut col_positions = HashMap::with_capacity(length);
                         for (pos, req) in ordering.iter().enumerate() {
-                            if let Some(col) = req.expr.as_any().downcast_ref::<Column>()
-                            {
+                            if let Some(col) = req.expr.downcast_ref::<Column>() {
                                 let nullable = col.nullable(&self.schema).unwrap_or(true);
                                 col_positions.insert(col.index(), (pos, nullable));
                             }
@@ -1134,7 +1132,7 @@ impl EquivalenceProperties {
             .iter()
             .flat_map(|(_, targets)| {
                 targets.iter().flat_map(|(target, _)| {
-                    target.as_any().downcast_ref::<Column>().map(|c| c.index())
+                    target.downcast_ref::<Column>().map(|c| c.index())
                 })
             })
             .collect::<Vec<_>>();
@@ -1392,10 +1390,10 @@ fn update_properties(
         // We have an intermediate (non-leaf) node, account for its children:
         let children_props = node.children.iter().map(|c| c.data.clone()).collect_vec();
         node.data = node.expr.get_properties(&children_props)?;
-    } else if node.expr.as_any().is::<Literal>() {
+    } else if node.expr.is::<Literal>() {
         // We have a Literal, which is one of the two possible leaf node types:
         node.data = node.expr.get_properties(&[])?;
-    } else if node.expr.as_any().is::<Column>() {
+    } else if node.expr.is::<Column>() {
         // We have a Column, which is the other possible leaf node type:
         node.data.range =
             Interval::make_unbounded(&node.expr.data_type(eq_properties.schema())?)?
@@ -1466,13 +1464,13 @@ fn get_expr_properties(
             range: Interval::make_unbounded(&expr.data_type(schema)?)?,
             preserves_lex_ordering: false,
         })
-    } else if expr.as_any().downcast_ref::<Column>().is_some() {
+    } else if expr.downcast_ref::<Column>().is_some() {
         Ok(ExprProperties {
             sort_properties: SortProperties::Unordered,
             range: Interval::make_unbounded(&expr.data_type(schema)?)?,
             preserves_lex_ordering: false,
         })
-    } else if let Some(literal) = expr.as_any().downcast_ref::<Literal>() {
+    } else if let Some(literal) = expr.downcast_ref::<Literal>() {
         Ok(ExprProperties {
             sort_properties: SortProperties::Singleton,
             range: literal.value().into(),
diff --git a/datafusion/physical-expr/src/expressions/binary.rs b/datafusion/physical-expr/src/expressions/binary.rs
index ecd8b91547..268a350a4d 100644
--- a/datafusion/physical-expr/src/expressions/binary.rs
+++ b/datafusion/physical-expr/src/expressions/binary.rs
@@ -20,7 +20,7 @@ mod kernels;
 use crate::PhysicalExpr;
 use crate::intervals::cp_solver::{propagate_arithmetic, propagate_comparison};
 use std::hash::Hash;
-use std::{any::Any, sync::Arc};
+use std::sync::Arc;
 
 use arrow::array::*;
 use arrow::compute::kernels::boolean::{and_kleene, or_kleene};
@@ -130,7 +130,7 @@ impl std::fmt::Display for BinaryExpr {
             expr: &dyn PhysicalExpr,
             precedence: u8,
         ) -> std::fmt::Result {
-            if let Some(child) = expr.as_any().downcast_ref::<BinaryExpr>() {
+            if let Some(child) = expr.downcast_ref::<BinaryExpr>() {
                 let p = child.op.precedence();
                 if p == 0 || p < precedence {
                     write!(f, "({child})")?;
@@ -252,11 +252,6 @@ fn duration_to_days(array: &ArrayRef) -> Result<ArrayRef> {
 }
 
 impl PhysicalExpr for BinaryExpr {
-    /// Return a reference to Any that can be used for downcasting
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     fn data_type(&self, input_schema: &Schema) -> Result<DataType> {
         BinaryTypeCoercer::new(
             &self.left.data_type(input_schema)?,
@@ -590,7 +585,7 @@ impl PhysicalExpr for BinaryExpr {
             expr: &dyn PhysicalExpr,
             precedence: u8,
         ) -> std::fmt::Result {
-            if let Some(child) = expr.as_any().downcast_ref::<BinaryExpr>() {
+            if let Some(child) = expr.downcast_ref::<BinaryExpr>() {
                 let p = child.op.precedence();
                 if p == 0 || p < precedence {
                     write!(f, "(")?;
@@ -4668,7 +4663,6 @@ mod tests {
         schema: &Schema,
     ) -> Result<BinaryExpr> {
         Ok(binary_op(left, op, right, schema)?
-            .as_any()
             .downcast_ref::<BinaryExpr>()
             .unwrap()
             .clone())
diff --git a/datafusion/physical-expr/src/expressions/case.rs b/datafusion/physical-expr/src/expressions/case.rs
index 4ac40df220..25aedecc2c 100644
--- a/datafusion/physical-expr/src/expressions/case.rs
+++ b/datafusion/physical-expr/src/expressions/case.rs
@@ -36,7 +36,7 @@ use datafusion_expr::ColumnarValue;
 use indexmap::{IndexMap, IndexSet};
 use std::borrow::Cow;
 use std::hash::Hash;
-use std::{any::Any, sync::Arc};
+use std::sync::Arc;
 
 use crate::expressions::case::literal_lookup_table::LiteralLookupTable;
 use arrow::compute::kernels::merge::{MergeIndex, merge, merge_n};
@@ -131,7 +131,7 @@ impl CaseBody {
         let mut used_column_indices = IndexSet::<usize>::new();
         let mut collect_column_indices = |expr: &Arc<dyn PhysicalExpr>| {
             expr.apply(|expr| {
-                if let Some(column) = expr.as_any().downcast_ref::<Column>() {
+                if let Some(column) = expr.downcast_ref::<Column>() {
                     used_column_indices.insert(column.index());
                 }
                 Ok(TreeNodeRecursion::Continue)
@@ -162,7 +162,7 @@ impl CaseBody {
         let project = |expr: &Arc<dyn PhysicalExpr>| -> Result<Arc<dyn PhysicalExpr>> {
             Arc::clone(expr)
                 .transform_down(|e| {
-                    if let Some(column) = e.as_any().downcast_ref::<Column>() {
+                    if let Some(column) = e.downcast_ref::<Column>() {
                         let original = column.index();
                         let projected = *column_index_map.get(&original).unwrap();
                         if projected != original {
@@ -281,7 +281,7 @@ impl std::fmt::Display for CaseExpr {
 /// this is limited to use with Column expressions but could potentially be used for other
 /// expressions in the future
 fn is_cheap_and_infallible(expr: &Arc<dyn PhysicalExpr>) -> bool {
-    expr.as_any().is::<Column>()
+    expr.is::<Column>()
 }
 
 /// Creates a [FilterPredicate] from a boolean array.
@@ -621,7 +621,7 @@ impl CaseExpr {
         // normalize null literals to None in the else_expr (this already happens
         // during SQL planning, but not necessarily for other use cases)
         let else_expr = match &else_expr {
-            Some(e) => match e.as_any().downcast_ref::<Literal>() {
+            Some(e) => match e.downcast_ref::<Literal>() {
                 Some(lit) if lit.value().is_null() => None,
                 _ => else_expr,
             },
@@ -659,9 +659,9 @@ impl CaseExpr {
             {
                 EvalMethod::InfallibleExprOrNull
             } else if body.when_then_expr.len() == 1
-                && body.when_then_expr[0].1.as_any().is::<Literal>()
+                && body.when_then_expr[0].1.is::<Literal>()
                 && body.else_expr.is_some()
-                && body.else_expr.as_ref().unwrap().as_any().is::<Literal>()
+                && body.else_expr.as_ref().unwrap().is::<Literal>()
             {
                 EvalMethod::ScalarOrScalar
             } else if body.when_then_expr.len() == 1 {
@@ -1194,11 +1194,6 @@ impl CaseExpr {
 }
 
 impl PhysicalExpr for CaseExpr {
-    /// Return a reference to Any that can be used for down-casting
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     fn data_type(&self, input_schema: &Schema) -> Result<DataType> {
         self.body.data_type(input_schema)
     }
@@ -2207,7 +2202,7 @@ mod tests {
 
         let expr2 = Arc::clone(&expr)
             .transform(|e| {
-                let transformed = match e.as_any().downcast_ref::<Literal>() {
+                let transformed = match e.downcast_ref::<Literal>() {
                     Some(lit_value) => match lit_value.value() {
                         ScalarValue::Utf8(Some(str_value)) => {
                             Some(lit(str_value.to_uppercase()))
@@ -2227,7 +2222,7 @@ mod tests {
 
         let expr3 = Arc::clone(&expr)
             .transform_down(|e| {
-                let transformed = match e.as_any().downcast_ref::<Literal>() {
+                let transformed = match e.downcast_ref::<Literal>() {
                     Some(lit_value) => match lit_value.value() {
                         ScalarValue::Utf8(Some(str_value)) => {
                             Some(lit(str_value.to_uppercase()))
diff --git a/datafusion/physical-expr/src/expressions/case/literal_lookup_table/mod.rs b/datafusion/physical-expr/src/expressions/case/literal_lookup_table/mod.rs
index 67b045f998..0d4291ccc9 100644
--- a/datafusion/physical-expr/src/expressions/case/literal_lookup_table/mod.rs
+++ b/datafusion/physical-expr/src/expressions/case/literal_lookup_table/mod.rs
@@ -95,8 +95,8 @@ impl LiteralLookupTable {
             .when_then_expr
             .iter()
             .map(|(when, then)| {
-                let when_maybe_literal = when.as_any().downcast_ref::<Literal>();
-                let then_maybe_literal = then.as_any().downcast_ref::<Literal>();
+                let when_maybe_literal = when.downcast_ref::<Literal>();
+                let then_maybe_literal = then.downcast_ref::<Literal>();
 
                 when_maybe_literal.zip(then_maybe_literal)
             })
@@ -142,7 +142,7 @@ impl LiteralLookupTable {
         };
 
         let else_value: ScalarValue = if let Some(else_expr) = &body.else_expr {
-            let literal = else_expr.as_any().downcast_ref::<Literal>()?;
+            let literal = else_expr.downcast_ref::<Literal>()?;
 
             literal.value().clone()
         } else {
diff --git a/datafusion/physical-expr/src/expressions/cast.rs b/datafusion/physical-expr/src/expressions/cast.rs
index 8d4167050b..ad214a89ce 100644
--- a/datafusion/physical-expr/src/expressions/cast.rs
+++ b/datafusion/physical-expr/src/expressions/cast.rs
@@ -15,7 +15,6 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use std::any::Any;
 use std::fmt;
 use std::hash::Hash;
 use std::sync::Arc;
@@ -229,11 +228,6 @@ impl fmt::Display for CastExpr {
 }
 
 impl PhysicalExpr for CastExpr {
-    /// Return a reference to Any that can be used for downcasting
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
         Ok(self.cast_type().clone())
     }
diff --git a/datafusion/physical-expr/src/expressions/column.rs b/datafusion/physical-expr/src/expressions/column.rs
index cf844790a0..ba8cd5e636 100644
--- a/datafusion/physical-expr/src/expressions/column.rs
+++ b/datafusion/physical-expr/src/expressions/column.rs
@@ -17,7 +17,6 @@
 
 //! Physical column reference: [`Column`]
 
-use std::any::Any;
 use std::hash::Hash;
 use std::sync::Arc;
 
@@ -106,11 +105,6 @@ impl std::fmt::Display for Column {
 }
 
 impl PhysicalExpr for Column {
-    /// Return a reference to Any that can be used for downcasting
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     /// Get the data type of this expression, given the schema of the input
     fn data_type(&self, input_schema: &Schema) -> Result<DataType> {
         self.bounds_check(input_schema)?;
@@ -189,7 +183,7 @@ pub fn with_new_schema(
 ) -> Result<Arc<dyn PhysicalExpr>> {
     Ok(expr
         .transform_up(|expr| {
-            if let Some(col) = expr.as_any().downcast_ref::<Column>() {
+            if let Some(col) = expr.downcast_ref::<Column>() {
                 let idx = col.index();
                 let Some(field) = schema.fields().get(idx) else {
                     return plan_err!(
diff --git a/datafusion/physical-expr/src/expressions/dynamic_filters.rs b/datafusion/physical-expr/src/expressions/dynamic_filters.rs
index d285f8b377..47398d87e2 100644
--- a/datafusion/physical-expr/src/expressions/dynamic_filters.rs
+++ b/datafusion/physical-expr/src/expressions/dynamic_filters.rs
@@ -16,7 +16,7 @@
 // under the License.
 
 use parking_lot::RwLock;
-use std::{any::Any, fmt::Display, hash::Hash, sync::Arc};
+use std::{fmt::Display, hash::Hash, sync::Arc};
 use tokio::sync::watch;
 
 use crate::PhysicalExpr;
@@ -349,10 +349,6 @@ impl DynamicFilterPhysicalExpr {
 }
 
 impl PhysicalExpr for DynamicFilterPhysicalExpr {
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     fn children(&self) -> Vec<&Arc<dyn PhysicalExpr>> {
         self.remapped_children
             .as_ref()
@@ -743,7 +739,6 @@ mod test {
         // with_new_children() is called which creates a new outer Arc but clones the inner Arc.
         let consumer1_expr = Arc::clone(&filter).with_new_children(vec![]).unwrap();
         let _consumer1 = consumer1_expr
-            .as_any()
             .downcast_ref::<DynamicFilterPhysicalExpr>()
             .expect("Should be DynamicFilterPhysicalExpr");
 
@@ -756,7 +751,6 @@ mod test {
         // Create another transformed consumer
         let consumer2_expr = Arc::clone(&filter).with_new_children(vec![]).unwrap();
         let _consumer2 = consumer2_expr
-            .as_any()
             .downcast_ref::<DynamicFilterPhysicalExpr>()
             .expect("Should be DynamicFilterPhysicalExpr");
 
diff --git a/datafusion/physical-expr/src/expressions/in_list.rs b/datafusion/physical-expr/src/expressions/in_list.rs
index 3487c7e709..2377edf937 100644
--- a/datafusion/physical-expr/src/expressions/in_list.rs
+++ b/datafusion/physical-expr/src/expressions/in_list.rs
@@ -17,7 +17,6 @@
 
 //! Implementation of `InList` expressions: [`InListExpr`]
 
-use std::any::Any;
 use std::fmt::Debug;
 use std::hash::{Hash, Hasher};
 use std::sync::Arc;
@@ -725,11 +724,6 @@ impl std::fmt::Display for InListExpr {
 }
 
 impl PhysicalExpr for InListExpr {
-    /// Return a reference to Any that can be used for downcasting
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
         Ok(DataType::Boolean)
     }
diff --git a/datafusion/physical-expr/src/expressions/is_not_null.rs b/datafusion/physical-expr/src/expressions/is_not_null.rs
index 62be8ebbc1..86acf0a4ea 100644
--- a/datafusion/physical-expr/src/expressions/is_not_null.rs
+++ b/datafusion/physical-expr/src/expressions/is_not_null.rs
@@ -26,7 +26,7 @@ use datafusion_common::Result;
 use datafusion_common::ScalarValue;
 use datafusion_expr::ColumnarValue;
 use std::hash::Hash;
-use std::{any::Any, sync::Arc};
+use std::sync::Arc;
 
 /// IS NOT NULL expression
 #[derive(Debug, Eq)]
@@ -67,11 +67,6 @@ impl std::fmt::Display for IsNotNullExpr {
 }
 
 impl PhysicalExpr for IsNotNullExpr {
-    /// Return a reference to Any that can be used for downcasting
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
         Ok(DataType::Boolean)
     }
diff --git a/datafusion/physical-expr/src/expressions/is_null.rs b/datafusion/physical-expr/src/expressions/is_null.rs
index 356fe2a866..8534ddb8d1 100644
--- a/datafusion/physical-expr/src/expressions/is_null.rs
+++ b/datafusion/physical-expr/src/expressions/is_null.rs
@@ -26,7 +26,7 @@ use datafusion_common::Result;
 use datafusion_common::ScalarValue;
 use datafusion_expr::ColumnarValue;
 use std::hash::Hash;
-use std::{any::Any, sync::Arc};
+use std::sync::Arc;
 
 /// IS NULL expression
 #[derive(Debug, Eq)]
@@ -67,11 +67,6 @@ impl std::fmt::Display for IsNullExpr {
 }
 
 impl PhysicalExpr for IsNullExpr {
-    /// Return a reference to Any that can be used for downcasting
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
         Ok(DataType::Boolean)
     }
diff --git a/datafusion/physical-expr/src/expressions/like.rs b/datafusion/physical-expr/src/expressions/like.rs
index fc49ca35f0..07ceb4e7d7 100644
--- a/datafusion/physical-expr/src/expressions/like.rs
+++ b/datafusion/physical-expr/src/expressions/like.rs
@@ -22,7 +22,7 @@ use datafusion_common::{Result, assert_or_internal_err};
 use datafusion_expr::{ColumnarValue, Operator};
 use datafusion_physical_expr_common::datum::apply_cmp;
 use std::hash::Hash;
-use std::{any::Any, sync::Arc};
+use std::sync::Arc;
 
 // Like expression
 #[derive(Debug, Eq)]
@@ -105,10 +105,6 @@ impl std::fmt::Display for LikeExpr {
 }
 
 impl PhysicalExpr for LikeExpr {
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
         Ok(DataType::Boolean)
     }
diff --git a/datafusion/physical-expr/src/expressions/literal.rs b/datafusion/physical-expr/src/expressions/literal.rs
index 7ba2dc14e4..7351158c54 100644
--- a/datafusion/physical-expr/src/expressions/literal.rs
+++ b/datafusion/physical-expr/src/expressions/literal.rs
@@ -17,7 +17,6 @@
 
 //! Literal expressions for physical operations
 
-use std::any::Any;
 use std::hash::Hash;
 use std::sync::Arc;
 
@@ -92,11 +91,6 @@ impl std::fmt::Display for Literal {
 }
 
 impl PhysicalExpr for Literal {
-    /// Return a reference to Any that can be used for downcasting
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
         Ok(self.value.data_type())
     }
diff --git a/datafusion/physical-expr/src/expressions/negative.rs b/datafusion/physical-expr/src/expressions/negative.rs
index c78bbe999e..da576f2872 100644
--- a/datafusion/physical-expr/src/expressions/negative.rs
+++ b/datafusion/physical-expr/src/expressions/negative.rs
@@ -17,7 +17,6 @@
 
 //! Negation (-) expression
 
-use std::any::Any;
 use std::hash::Hash;
 use std::sync::Arc;
 
@@ -79,11 +78,6 @@ impl std::fmt::Display for NegativeExpr {
 }
 
 impl PhysicalExpr for NegativeExpr {
-    /// Return a reference to Any that can be used for downcasting
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     fn data_type(&self, input_schema: &Schema) -> Result<DataType> {
         self.arg.data_type(input_schema)
     }
diff --git a/datafusion/physical-expr/src/expressions/no_op.rs b/datafusion/physical-expr/src/expressions/no_op.rs
index ff44a60a86..c866c6ab07 100644
--- a/datafusion/physical-expr/src/expressions/no_op.rs
+++ b/datafusion/physical-expr/src/expressions/no_op.rs
@@ -17,7 +17,6 @@
 
 //! NoOp placeholder for physical operations
 
-use std::any::Any;
 use std::hash::Hash;
 use std::sync::Arc;
 
@@ -49,11 +48,6 @@ impl std::fmt::Display for NoOp {
 }
 
 impl PhysicalExpr for NoOp {
-    /// Return a reference to Any that can be used for downcasting
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
         Ok(DataType::Null)
     }
diff --git a/datafusion/physical-expr/src/expressions/not.rs b/datafusion/physical-expr/src/expressions/not.rs
index a29ab2ff40..917d3a9535 100644
--- a/datafusion/physical-expr/src/expressions/not.rs
+++ b/datafusion/physical-expr/src/expressions/not.rs
@@ -17,7 +17,6 @@
 
 //! Not expression
 
-use std::any::Any;
 use std::fmt;
 use std::hash::Hash;
 use std::sync::Arc;
@@ -70,11 +69,6 @@ impl fmt::Display for NotExpr {
 }
 
 impl PhysicalExpr for NotExpr {
-    /// Return a reference to Any that can be used for downcasting
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
         Ok(DataType::Boolean)
     }
diff --git a/datafusion/physical-expr/src/expressions/try_cast.rs b/datafusion/physical-expr/src/expressions/try_cast.rs
index 306f14b48f..ba59d113ac 100644
--- a/datafusion/physical-expr/src/expressions/try_cast.rs
+++ b/datafusion/physical-expr/src/expressions/try_cast.rs
@@ -15,7 +15,6 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use std::any::Any;
 use std::fmt;
 use std::hash::Hash;
 use std::sync::Arc;
@@ -77,11 +76,6 @@ impl fmt::Display for TryCastExpr {
 }
 
 impl PhysicalExpr for TryCastExpr {
-    /// Return a reference to Any that can be used for downcasting
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
         Ok(self.cast_type.clone())
     }
diff --git a/datafusion/physical-expr/src/expressions/unknown_column.rs b/datafusion/physical-expr/src/expressions/unknown_column.rs
index f06d880985..4969fc3374 100644
--- a/datafusion/physical-expr/src/expressions/unknown_column.rs
+++ b/datafusion/physical-expr/src/expressions/unknown_column.rs
@@ -17,7 +17,6 @@
 
 //! UnKnownColumn expression
 
-use std::any::Any;
 use std::hash::{Hash, Hasher};
 use std::sync::Arc;
 
@@ -56,11 +55,6 @@ impl std::fmt::Display for UnKnownColumn {
 }
 
 impl PhysicalExpr for UnKnownColumn {
-    /// Return a reference to Any that can be used for downcasting
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     /// Get the data type of this expression, given the schema of the input
     fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
         Ok(DataType::Null)
diff --git a/datafusion/physical-expr/src/intervals/cp_solver.rs b/datafusion/physical-expr/src/intervals/cp_solver.rs
index cb4c2e47bc..aee65f35dc 100644
--- a/datafusion/physical-expr/src/intervals/cp_solver.rs
+++ b/datafusion/physical-expr/src/intervals/cp_solver.rs
@@ -220,7 +220,7 @@ impl ExprIntervalGraphNode {
     /// any other expression starts with an indefinite interval (`[-∞, ∞]`).
     pub fn make_node(node: &ExprTreeNode<NodeIndex>, schema: &Schema) -> Result<Self> {
         let expr = Arc::clone(&node.expr);
-        if let Some(literal) = expr.as_any().downcast_ref::<Literal>() {
+        if let Some(literal) = expr.downcast_ref::<Literal>() {
             let value = literal.value();
             Interval::try_new(value.clone(), value.clone())
                 .map(|interval| Self::new_with_interval(expr, interval))
@@ -646,7 +646,6 @@ impl ExprIntervalGraph {
             if node_interval == &Interval::TRUE
                 && self.graph[node]
                     .expr
-                    .as_any()
                     .downcast_ref::<BinaryExpr>()
                     .is_some_and(|expr| expr.op() == &Operator::Or)
             {
@@ -892,12 +891,12 @@ mod tests {
                     PropagationResult::Success,
                     &Schema::new(vec![
                         Field::new(
-                            left_col.as_any().downcast_ref::<Column>().unwrap().name(),
+                            left_col.downcast_ref::<Column>().unwrap().name(),
                             DataType::$SCALAR,
                             true,
                         ),
                         Field::new(
-                            right_col.as_any().downcast_ref::<Column>().unwrap().name(),
+                            right_col.downcast_ref::<Column>().unwrap().name(),
                             DataType::$SCALAR,
                             true,
                         ),
@@ -939,16 +938,8 @@ mod tests {
             Interval::make(Some(100), None)?,
             PropagationResult::Infeasible,
             &Schema::new(vec![
-                Field::new(
-                    left_col.as_any().downcast_ref::<Column>().unwrap().name(),
-                    DataType::Int32,
-                    true,
-                ),
-                Field::new(
-                    right_col.as_any().downcast_ref::<Column>().unwrap().name(),
-                    DataType::Int32,
-                    true,
-                ),
+                Field::new(left_col.name(), DataType::Int32, true),
+                Field::new(right_col.name(), DataType::Int32, true),
             ]),
         )
     }
diff --git a/datafusion/physical-expr/src/intervals/utils.rs b/datafusion/physical-expr/src/intervals/utils.rs
index 3cada63a34..fb039a6e5f 100644
--- a/datafusion/physical-expr/src/intervals/utils.rs
+++ b/datafusion/physical-expr/src/intervals/utils.rs
@@ -36,26 +36,25 @@ use datafusion_expr::interval_arithmetic::Interval;
 /// will relax as more types of `PhysicalExpr`s and `Operator`s are supported.
 /// Currently, [`CastExpr`], [`NegativeExpr`], [`BinaryExpr`], [`Column`] and [`Literal`] are supported.
 pub fn check_support(expr: &Arc<dyn PhysicalExpr>, schema: &SchemaRef) -> bool {
-    let expr_any = expr.as_any();
-    if let Some(binary_expr) = expr_any.downcast_ref::<BinaryExpr>() {
+    if let Some(binary_expr) = expr.downcast_ref::<BinaryExpr>() {
         is_operator_supported(binary_expr.op())
             && check_support(binary_expr.left(), schema)
             && check_support(binary_expr.right(), schema)
-    } else if let Some(column) = expr_any.downcast_ref::<Column>() {
+    } else if let Some(column) = expr.downcast_ref::<Column>() {
         if let Ok(field) = schema.field_with_name(column.name()) {
             is_datatype_supported(field.data_type())
         } else {
             false
         }
-    } else if let Some(literal) = expr_any.downcast_ref::<Literal>() {
+    } else if let Some(literal) = expr.downcast_ref::<Literal>() {
         if let Ok(dt) = literal.data_type(schema) {
             is_datatype_supported(&dt)
         } else {
             false
         }
-    } else if let Some(cast) = expr_any.downcast_ref::<CastExpr>() {
+    } else if let Some(cast) = expr.downcast_ref::<CastExpr>() {
         check_support(cast.expr(), schema)
-    } else if let Some(negative) = expr_any.downcast_ref::<NegativeExpr>() {
+    } else if let Some(negative) = expr.downcast_ref::<NegativeExpr>() {
         check_support(negative.arg(), schema)
     } else {
         false
diff --git a/datafusion/physical-expr/src/physical_expr.rs b/datafusion/physical-expr/src/physical_expr.rs
index a03b58e0b5..77ede76e1d 100644
--- a/datafusion/physical-expr/src/physical_expr.rs
+++ b/datafusion/physical-expr/src/physical_expr.rs
@@ -38,7 +38,7 @@ pub fn add_offset_to_expr(
     expr: Arc<dyn PhysicalExpr>,
     offset: isize,
 ) -> Result<Arc<dyn PhysicalExpr>> {
-    expr.transform_down(|e| match e.as_any().downcast_ref::<Column>() {
+    expr.transform_down(|e| match e.downcast_ref::<Column>() {
         Some(col) => {
             let Some(idx) = col.index().checked_add_signed(offset) else {
                 return plan_err!("Column index overflow");
@@ -244,7 +244,6 @@ mod tests {
     use datafusion_common::ScalarValue;
     use datafusion_expr::ColumnarValue;
     use datafusion_expr::Operator;
-    use std::any::Any;
     use std::fmt;
 
     #[test]
@@ -394,10 +393,6 @@ mod tests {
     }
 
     impl PhysicalExpr for MockVolatileExpr {
-        fn as_any(&self) -> &dyn Any {
-            self
-        }
-
         fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
             Ok(DataType::Boolean)
         }
diff --git a/datafusion/physical-expr/src/planner.rs b/datafusion/physical-expr/src/planner.rs
index 3227e20f68..0880f1a139 100644
--- a/datafusion/physical-expr/src/planner.rs
+++ b/datafusion/physical-expr/src/planner.rs
@@ -447,7 +447,6 @@ mod tests {
 
     fn as_planner_cast(physical: &Arc<dyn PhysicalExpr>) -> &expressions::CastExpr {
         physical
-            .as_any()
             .downcast_ref::<expressions::CastExpr>()
             .expect("planner should lower logical CAST to CastExpr")
     }
diff --git a/datafusion/physical-expr/src/projection.rs b/datafusion/physical-expr/src/projection.rs
index 5f7b5a2e96..ed4f2ff0bd 100644
--- a/datafusion/physical-expr/src/projection.rs
+++ b/datafusion/physical-expr/src/projection.rs
@@ -434,7 +434,6 @@ impl ProjectionExprs {
             .iter()
             .map(|e| {
                 e.expr
-                    .as_any()
                     .downcast_ref::<Column>()
                     .expect("Expected column reference in projection")
                     .index()
@@ -661,9 +660,9 @@ impl ProjectionExprs {
 
         for proj_expr in self.exprs.iter() {
             let expr = &proj_expr.expr;
-            let col_stats = if let Some(col) = expr.as_any().downcast_ref::<Column>() {
+            let col_stats = if let Some(col) = expr.downcast_ref::<Column>() {
                 std::mem::take(&mut stats.column_statistics[col.index()])
-            } else if let Some(literal) = expr.as_any().downcast_ref::<Literal>() {
+            } else if let Some(literal) = expr.downcast_ref::<Literal>() {
                 // Handle literal expressions (constants) by calculating proper statistics
                 let data_type = expr.data_type(output_schema)?;
 
@@ -926,7 +925,7 @@ pub fn update_expr(
                 return Ok(Transformed::no(expr));
             }
 
-            let Some(column) = expr.as_any().downcast_ref::<Column>() else {
+            let Some(column) = expr.downcast_ref::<Column>() else {
                 return Ok(Transformed::no(expr));
             };
             if unproject {
@@ -948,7 +947,7 @@ pub fn update_expr(
                     .iter()
                     .enumerate()
                     .find_map(|(index, proj_expr)| {
-                        proj_expr.expr.as_any().downcast_ref::<Column>().and_then(
+                        proj_expr.expr.downcast_ref::<Column>().and_then(
                             |projected_column| {
                                 (column.name().eq(projected_column.name())
                                     && column.index() == projected_column.index())
@@ -1044,7 +1043,7 @@ impl ProjectionMapping {
         let mut map = IndexMap::<_, ProjectionTargets>::new();
         for (expr_idx, (expr, name)) in expr.into_iter().enumerate() {
             let target_expr = Arc::new(Column::new(&name, expr_idx)) as _;
-            let source_expr = expr.transform_down(|e| match e.as_any().downcast_ref::<Column>() {
+            let source_expr = expr.transform_down(|e| match e.downcast_ref::<Column>() {
                 Some(col) => {
                     // Sometimes, an expression and its name in the input_schema
                     // doesn't match. This can cause problems, so we make sure
@@ -1075,17 +1074,11 @@ impl ProjectionMapping {
             //   p.ticker → get_field(col("details"), "ticker")
             // enabling the optimizer to know that sorting by
             // `details.ticker` is equivalent to sorting by `p.ticker`.
-            if let Some(func_expr) =
-                source_expr.as_any().downcast_ref::<ScalarFunctionExpr>()
-            {
+            if let Some(func_expr) = source_expr.downcast_ref::<ScalarFunctionExpr>() {
                 let literal_args: Vec<Option<ScalarValue>> = func_expr
                     .args()
                     .iter()
-                    .map(|arg| {
-                        arg.as_any()
-                            .downcast_ref::<Literal>()
-                            .map(|l| l.value().clone())
-                    })
+                    .map(|arg| arg.downcast_ref::<Literal>().map(|l| l.value().clone()))
                     .collect();
 
                 if let Some(field_mapping) =
@@ -1227,7 +1220,7 @@ pub fn project_ordering(
     let mut projected_exprs = vec![];
     for PhysicalSortExpr { expr, options } in ordering.iter() {
         let transformed = Arc::clone(expr).transform_up(|expr| {
-            let Some(col) = expr.as_any().downcast_ref::<Column>() else {
+            let Some(col) = expr.downcast_ref::<Column>() else {
                 return Ok(Transformed::no(expr));
             };
 
@@ -1285,7 +1278,7 @@ pub(crate) mod tests {
             for (target, _) in targets.iter() {
                 // Skip non-Column targets (e.g. struct field decomposition
                 // entries which are ScalarFunctionExpr targets).
-                let Some(column) = target.as_any().downcast_ref::<Column>() else {
+                let Some(column) = target.downcast_ref::<Column>() else {
                     continue;
                 };
                 fields.push(Field::new(column.name(), data_type.clone(), nullable));
@@ -2549,11 +2542,7 @@ pub(crate) mod tests {
 
         let result_expr = result.unwrap();
         assert_eq!(
-            result_expr
-                .as_any()
-                .downcast_ref::<Literal>()
-                .unwrap()
-                .value(),
+            result_expr.downcast_ref::<Literal>().unwrap().value(),
             &ScalarValue::Int64(Some(42))
         );
 
@@ -2582,17 +2571,15 @@ pub(crate) mod tests {
 
         let result_expr = result.unwrap();
         let binary = result_expr
-            .as_any()
             .downcast_ref::<BinaryExpr>()
             .expect("Should be a BinaryExpr");
 
         // Left side should still be the literal
-        assert!(binary.left().as_any().downcast_ref::<Literal>().is_some());
+        assert!(binary.left().downcast_ref::<Literal>().is_some());
 
         // Right side should be updated to reference column at index 5
         let right_col = binary
             .right()
-            .as_any()
             .downcast_ref::<Column>()
             .expect("Right should be a Column");
         assert_eq!(right_col.index(), 5);
diff --git a/datafusion/physical-expr/src/scalar_function.rs b/datafusion/physical-expr/src/scalar_function.rs
index 5e6e0aa97f..418d005c97 100644
--- a/datafusion/physical-expr/src/scalar_function.rs
+++ b/datafusion/physical-expr/src/scalar_function.rs
@@ -29,7 +29,6 @@
 //! This module also has a set of coercion rules to improve user experience: if an argument i32 is passed
 //! to a function that supports f64, it is coerced to f64.
 
-use std::any::Any;
 use std::fmt::{self, Debug, Formatter};
 use std::hash::{Hash, Hasher};
 use std::sync::Arc;
@@ -105,11 +104,7 @@ impl ScalarFunctionExpr {
 
         let arguments = args
             .iter()
-            .map(|e| {
-                e.as_any()
-                    .downcast_ref::<Literal>()
-                    .map(|literal| literal.value())
-            })
+            .map(|e| e.downcast_ref::<Literal>().map(|literal| literal.value()))
             .collect::<Vec<_>>();
         let ret_args = ReturnFieldArgs {
             arg_fields: &arg_fields,
@@ -171,7 +166,7 @@ impl ScalarFunctionExpr {
     where
         T: ScalarUDFImpl,
     {
-        match expr.as_any().downcast_ref::<ScalarFunctionExpr>() {
+        match expr.downcast_ref::<ScalarFunctionExpr>() {
             Some(scalar_expr) if scalar_expr.fun().inner().is::<T>() => Some(scalar_expr),
             _ => None,
         }
@@ -230,11 +225,6 @@ fn sorted_config_entries(config_options: &ConfigOptions) -> Vec<ConfigEntry> {
 }
 
 impl PhysicalExpr for ScalarFunctionExpr {
-    /// Return a reference to Any that can be used for downcasting
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
         Ok(self.return_field.data_type().clone())
     }
diff --git a/datafusion/physical-expr/src/simplifier/const_evaluator.rs b/datafusion/physical-expr/src/simplifier/const_evaluator.rs
index 148a9bdcec..34c080bfa0 100644
--- a/datafusion/physical-expr/src/simplifier/const_evaluator.rs
+++ b/datafusion/physical-expr/src/simplifier/const_evaluator.rs
@@ -48,7 +48,7 @@ pub fn simplify_const_expr(
 ) -> Result<Transformed<Arc<dyn PhysicalExpr>>> {
     let batch = create_dummy_batch()?;
     // If expr is already a const literal or can't be evaluated into one.
-    if expr.as_any().is::<Literal>() || (!can_evaluate_as_constant(&expr)) {
+    if expr.is::<Literal>() || (!can_evaluate_as_constant(&expr)) {
         return Ok(Transformed::no(expr));
     }
 
@@ -90,12 +90,12 @@ pub(crate) fn simplify_const_expr_immediate(
     batch: &RecordBatch,
 ) -> Result<Transformed<Arc<dyn PhysicalExpr>>> {
     // Already a literal - nothing to do
-    if expr.as_any().is::<Literal>() {
+    if expr.is::<Literal>() {
         return Ok(Transformed::no(expr));
     }
 
     // Column references cannot be evaluated at plan time
-    if expr.as_any().is::<Column>() {
+    if expr.is::<Column>() {
         return Ok(Transformed::no(expr));
     }
 
@@ -107,10 +107,7 @@ pub(crate) fn simplify_const_expr_immediate(
     // Since transform visits bottom-up, children have already been simplified.
     // If all children are now Literals, this node can be const-evaluated.
     // This is O(k) where k = number of children, instead of O(subtree).
-    let all_children_literal = expr
-        .children()
-        .iter()
-        .all(|child| child.as_any().is::<Literal>());
+    let all_children_literal = expr.children().iter().all(|child| child.is::<Literal>());
 
     if !all_children_literal {
         return Ok(Transformed::no(expr));
@@ -169,7 +166,7 @@ fn can_evaluate_as_constant(expr: &Arc<dyn PhysicalExpr>) -> bool {
     let mut can_evaluate = true;
 
     expr.apply(|e| {
-        if e.as_any().is::<Column>() || e.is_volatile_node() {
+        if e.is::<Column>() || e.is_volatile_node() {
             can_evaluate = false;
             Ok(TreeNodeRecursion::Stop)
         } else {
@@ -189,7 +186,7 @@ fn can_evaluate_as_constant(expr: &Arc<dyn PhysicalExpr>) -> bool {
 pub fn has_column_references(expr: &Arc<dyn PhysicalExpr>) -> bool {
     let mut has_columns = false;
     expr.apply(|expr| {
-        if expr.as_any().downcast_ref::<Column>().is_some() {
+        if expr.downcast_ref::<Column>().is_some() {
             has_columns = true;
             Ok(TreeNodeRecursion::Stop)
         } else {
diff --git a/datafusion/physical-expr/src/simplifier/mod.rs b/datafusion/physical-expr/src/simplifier/mod.rs
index 16c2f5255f..272c1f595a 100644
--- a/datafusion/physical-expr/src/simplifier/mod.rs
+++ b/datafusion/physical-expr/src/simplifier/mod.rs
@@ -119,15 +119,13 @@ mod tests {
 
     /// Helper function to extract a Literal from a PhysicalExpr
     fn as_literal(expr: &Arc<dyn PhysicalExpr>) -> &Literal {
-        expr.as_any()
-            .downcast_ref::<Literal>()
+        expr.downcast_ref::<Literal>()
             .unwrap_or_else(|| panic!("Expected Literal, got: {expr}"))
     }
 
     /// Helper function to extract a BinaryExpr from a PhysicalExpr
     fn as_binary(expr: &Arc<dyn PhysicalExpr>) -> &BinaryExpr {
-        expr.as_any()
-            .downcast_ref::<BinaryExpr>()
+        expr.downcast_ref::<BinaryExpr>()
             .unwrap_or_else(|| panic!("Expected BinaryExpr, got: {expr}"))
     }
 
@@ -164,8 +162,8 @@ mod tests {
         // Should be optimized to: c2 != INT64(99) (c2 is INT64, literal cast to match)
         let left_expr = optimized_binary.left();
         assert!(
-            left_expr.as_any().downcast_ref::<CastExpr>().is_none()
-                && left_expr.as_any().downcast_ref::<TryCastExpr>().is_none()
+            left_expr.downcast_ref::<CastExpr>().is_none()
+                && left_expr.downcast_ref::<TryCastExpr>().is_none()
         );
         let right_literal = as_literal(optimized_binary.right());
         assert_eq!(right_literal.value(), &ScalarValue::Int64(Some(99)));
@@ -198,11 +196,8 @@ mod tests {
         let left_binary = as_binary(or_binary.left());
         let left_left_expr = left_binary.left();
         assert!(
-            left_left_expr.as_any().downcast_ref::<CastExpr>().is_none()
-                && left_left_expr
-                    .as_any()
-                    .downcast_ref::<TryCastExpr>()
-                    .is_none()
+            left_left_expr.downcast_ref::<CastExpr>().is_none()
+                && left_left_expr.downcast_ref::<TryCastExpr>().is_none()
         );
         let left_literal = as_literal(left_binary.right());
         assert_eq!(left_literal.value(), &ScalarValue::Int32(Some(5)));
@@ -211,14 +206,8 @@ mod tests {
         let right_binary = as_binary(or_binary.right());
         let right_left_expr = right_binary.left();
         assert!(
-            right_left_expr
-                .as_any()
-                .downcast_ref::<CastExpr>()
-                .is_none()
-                && right_left_expr
-                    .as_any()
-                    .downcast_ref::<TryCastExpr>()
-                    .is_none()
+            right_left_expr.downcast_ref::<CastExpr>().is_none()
+                && right_left_expr.downcast_ref::<TryCastExpr>().is_none()
         );
         let right_literal = as_literal(right_binary.right());
         assert_eq!(right_literal.value(), &ScalarValue::Int64(Some(10)));
@@ -476,7 +465,7 @@ mod tests {
         // If we just let `expr` go out of scope, Rust's recursive Drop will blow the stack
         // even with recursive_protection, because Drop doesn't use the #[recursive] attribute.
         // We peel off layers one by one to avoid deep recursion in Drop.
-        while let Some(not_expr) = expr.as_any().downcast_ref::<NotExpr>() {
+        while let Some(not_expr) = expr.downcast_ref::<NotExpr>() {
             // Clone the child (Arc increment).
             // Now child has 2 refs: one in parent, one in `child`.
             let child = Arc::clone(not_expr.arg());
@@ -577,7 +566,7 @@ mod tests {
         ));
         let result = simplifier.simplify(expr).unwrap();
         // Should remain a BinaryExpr, not become a Literal
-        assert!(result.as_any().downcast_ref::<BinaryExpr>().is_some());
+        assert!(result.downcast_ref::<BinaryExpr>().is_some());
     }
 
     #[test]
diff --git a/datafusion/physical-expr/src/simplifier/not.rs b/datafusion/physical-expr/src/simplifier/not.rs
index 709260aa48..886cadd6a2 100644
--- a/datafusion/physical-expr/src/simplifier/not.rs
+++ b/datafusion/physical-expr/src/simplifier/not.rs
@@ -52,7 +52,7 @@ pub fn simplify_not_expr(
     schema: &Schema,
 ) -> Result<Transformed<Arc<dyn PhysicalExpr>>> {
     // Check if this is a NOT expression
-    let not_expr = match expr.as_any().downcast_ref::<NotExpr>() {
+    let not_expr = match expr.downcast_ref::<NotExpr>() {
         Some(not_expr) => not_expr,
         None => return Ok(Transformed::no(expr)),
     };
@@ -60,12 +60,12 @@ pub fn simplify_not_expr(
     let inner_expr = not_expr.arg();
 
     // Handle NOT(NOT(expr)) -> expr (double negation elimination)
-    if let Some(inner_not) = inner_expr.as_any().downcast_ref::<NotExpr>() {
+    if let Some(inner_not) = inner_expr.downcast_ref::<NotExpr>() {
         return Ok(Transformed::yes(Arc::clone(inner_not.arg())));
     }
 
     // Handle NOT(literal) -> !literal
-    if let Some(literal) = inner_expr.as_any().downcast_ref::<Literal>() {
+    if let Some(literal) = inner_expr.downcast_ref::<Literal>() {
         if let ScalarValue::Boolean(Some(val)) = literal.value() {
             return Ok(Transformed::yes(lit(ScalarValue::Boolean(Some(!val)))));
         }
@@ -75,7 +75,7 @@ pub fn simplify_not_expr(
     }
 
     // Handle NOT(IN list) -> NOT IN list
-    if let Some(in_list_expr) = inner_expr.as_any().downcast_ref::<InListExpr>() {
+    if let Some(in_list_expr) = inner_expr.downcast_ref::<InListExpr>() {
         let negated = !in_list_expr.negated();
         let new_in_list = in_list(
             Arc::clone(in_list_expr.expr()),
@@ -87,7 +87,7 @@ pub fn simplify_not_expr(
     }
 
     // Handle NOT(binary_expr)
-    if let Some(binary_expr) = inner_expr.as_any().downcast_ref::<BinaryExpr>() {
+    if let Some(binary_expr) = inner_expr.downcast_ref::<BinaryExpr>() {
         if let Some(negated_op) = binary_expr.op().negate() {
             let new_binary = Arc::new(BinaryExpr::new(
                 Arc::clone(binary_expr.left()),
diff --git a/datafusion/physical-expr/src/simplifier/unwrap_cast.rs b/datafusion/physical-expr/src/simplifier/unwrap_cast.rs
index f0f48ecf97..4f4dfb2c20 100644
--- a/datafusion/physical-expr/src/simplifier/unwrap_cast.rs
+++ b/datafusion/physical-expr/src/simplifier/unwrap_cast.rs
@@ -46,7 +46,7 @@ pub(crate) fn unwrap_cast_in_comparison(
     expr: Arc<dyn PhysicalExpr>,
     schema: &Schema,
 ) -> Result<Transformed<Arc<dyn PhysicalExpr>>> {
-    if let Some(binary) = expr.as_any().downcast_ref::<BinaryExpr>()
+    if let Some(binary) = expr.downcast_ref::<BinaryExpr>()
         && let Some(unwrapped) = try_unwrap_cast_binary(binary, schema)?
     {
         return Ok(Transformed::yes(unwrapped));
@@ -62,7 +62,7 @@ fn try_unwrap_cast_binary(
     // Case 1: cast(left_expr) op literal
     if let (Some((inner_expr, _cast_type)), Some(literal)) = (
         extract_cast_info(binary.left()),
-        binary.right().as_any().downcast_ref::<Literal>(),
+        binary.right().downcast_ref::<Literal>(),
     ) && binary.op().supports_propagation()
         && let Some(unwrapped) = try_unwrap_cast_comparison(
             Arc::clone(inner_expr),
@@ -76,7 +76,7 @@ fn try_unwrap_cast_binary(
 
     // Case 2: literal op cast(right_expr)
     if let (Some(literal), Some((inner_expr, _cast_type))) = (
-        binary.left().as_any().downcast_ref::<Literal>(),
+        binary.left().downcast_ref::<Literal>(),
         extract_cast_info(binary.right()),
     ) {
         // For literal op cast(expr), we need to swap the operator
@@ -105,9 +105,9 @@ fn try_unwrap_cast_binary(
 fn extract_cast_info(
     expr: &Arc<dyn PhysicalExpr>,
 ) -> Option<(&Arc<dyn PhysicalExpr>, &DataType)> {
-    if let Some(cast) = expr.as_any().downcast_ref::<CastExpr>() {
+    if let Some(cast) = expr.downcast_ref::<CastExpr>() {
         Some((cast.expr(), cast.cast_type()))
-    } else if let Some(try_cast) = expr.as_any().downcast_ref::<TryCastExpr>() {
+    } else if let Some(try_cast) = expr.downcast_ref::<TryCastExpr>() {
         Some((try_cast.expr(), try_cast.cast_type()))
     } else {
         None
@@ -143,20 +143,19 @@ mod tests {
 
     /// Check if an expression is a cast expression
     fn is_cast_expr(expr: &Arc<dyn PhysicalExpr>) -> bool {
-        expr.as_any().downcast_ref::<CastExpr>().is_some()
-            || expr.as_any().downcast_ref::<TryCastExpr>().is_some()
+        expr.downcast_ref::<CastExpr>().is_some()
+            || expr.downcast_ref::<TryCastExpr>().is_some()
     }
 
     /// Check if a binary expression is suitable for cast unwrapping
     fn is_binary_expr_with_cast_and_literal(binary: &BinaryExpr) -> bool {
         // Check if left is cast and right is literal
         let left_cast_right_literal = is_cast_expr(binary.left())
-            && binary.right().as_any().downcast_ref::<Literal>().is_some();
+            && binary.right().downcast_ref::<Literal>().is_some();
 
         // Check if left is literal and right is cast
-        let left_literal_right_cast =
-            binary.left().as_any().downcast_ref::<Literal>().is_some()
-                && is_cast_expr(binary.right());
+        let left_literal_right_cast = binary.left().downcast_ref::<Literal>().is_some()
+            && is_cast_expr(binary.right());
 
         left_cast_right_literal || left_literal_right_cast
     }
@@ -188,17 +187,13 @@ mod tests {
 
         // The result should be: c1 > INT32(10)
         let optimized = result.data;
-        let optimized_binary = optimized.as_any().downcast_ref::<BinaryExpr>().unwrap();
+        let optimized_binary = optimized.downcast_ref::<BinaryExpr>().unwrap();
 
         // Check that left side is no longer a cast
         assert!(!is_cast_expr(optimized_binary.left()));
 
         // Check that right side is a literal with the correct type and value
-        let right_literal = optimized_binary
-            .right()
-            .as_any()
-            .downcast_ref::<Literal>()
-            .unwrap();
+        let right_literal = optimized_binary.right().downcast_ref::<Literal>().unwrap();
         assert_eq!(right_literal.value(), &ScalarValue::Int32(Some(10)));
     }
 
@@ -221,7 +216,7 @@ mod tests {
 
         // The result should be equivalent to: c1 > INT32(10)
         let optimized = result.data;
-        let optimized_binary = optimized.as_any().downcast_ref::<BinaryExpr>().unwrap();
+        let optimized_binary = optimized.downcast_ref::<BinaryExpr>().unwrap();
 
         // Check the operator was swapped
         assert_eq!(*optimized_binary.op(), Operator::Gt);
@@ -254,9 +249,7 @@ mod tests {
         let literal_expr = lit(10i64);
         let binary_expr =
             Arc::new(BinaryExpr::new(cast_expr, Operator::Gt, literal_expr));
-        let binary_ref = binary_expr.as_any().downcast_ref::<BinaryExpr>().unwrap();
-
-        assert!(is_binary_expr_with_cast_and_literal(binary_ref));
+        assert!(is_binary_expr_with_cast_and_literal(&binary_expr));
     }
 
     #[test]
@@ -288,7 +281,7 @@ mod tests {
 
         // The result should be: decimal_col >= Decimal128(400, 9, 2)
         let optimized = result.data;
-        let optimized_binary = optimized.as_any().downcast_ref::<BinaryExpr>().unwrap();
+        let optimized_binary = optimized.downcast_ref::<BinaryExpr>().unwrap();
 
         // Check operator was swapped correctly
         assert_eq!(*optimized_binary.op(), Operator::GtEq);
@@ -297,11 +290,7 @@ mod tests {
         assert!(!is_cast_expr(optimized_binary.left()));
 
         // Check that right side is a literal with the correct type
-        let right_literal = optimized_binary
-            .right()
-            .as_any()
-            .downcast_ref::<Literal>()
-            .unwrap();
+        let right_literal = optimized_binary.right().downcast_ref::<Literal>().unwrap();
         assert_eq!(
             right_literal.value().data_type(),
             DataType::Decimal128(9, 2)
@@ -337,8 +326,7 @@ mod tests {
             assert!(result.transformed);
 
             let optimized = result.data;
-            let optimized_binary =
-                optimized.as_any().downcast_ref::<BinaryExpr>().unwrap();
+            let optimized_binary = optimized.downcast_ref::<BinaryExpr>().unwrap();
 
             // Check the operator was swapped correctly
             assert_eq!(
@@ -351,11 +339,8 @@ mod tests {
             assert!(!is_cast_expr(optimized_binary.left()));
 
             // Check that the literal was cast to the column type
-            let right_literal = optimized_binary
-                .right()
-                .as_any()
-                .downcast_ref::<Literal>()
-                .unwrap();
+            let right_literal =
+                optimized_binary.right().downcast_ref::<Literal>().unwrap();
             assert_eq!(right_literal.value(), &ScalarValue::Int32(Some(100)));
         }
     }
@@ -428,12 +413,8 @@ mod tests {
 
         // Verify the NULL was cast to the column type
         let optimized = result.data;
-        let optimized_binary = optimized.as_any().downcast_ref::<BinaryExpr>().unwrap();
-        let right_literal = optimized_binary
-            .right()
-            .as_any()
-            .downcast_ref::<Literal>()
-            .unwrap();
+        let optimized_binary = optimized.downcast_ref::<BinaryExpr>().unwrap();
+        let right_literal = optimized_binary.right().downcast_ref::<Literal>().unwrap();
         assert_eq!(right_literal.value(), &ScalarValue::Int32(None));
     }
 
@@ -488,20 +469,12 @@ mod tests {
 
         // Verify the AND operator is preserved
         let optimized = result.data;
-        let and_binary = optimized.as_any().downcast_ref::<BinaryExpr>().unwrap();
+        let and_binary = optimized.downcast_ref::<BinaryExpr>().unwrap();
         assert_eq!(*and_binary.op(), Operator::And);
 
         // Both sides should have their casts unwrapped
-        let left_binary = and_binary
-            .left()
-            .as_any()
-            .downcast_ref::<BinaryExpr>()
-            .unwrap();
-        let right_binary = and_binary
-            .right()
-            .as_any()
-            .downcast_ref::<BinaryExpr>()
-            .unwrap();
+        let left_binary = and_binary.left().downcast_ref::<BinaryExpr>().unwrap();
+        let right_binary = and_binary.right().downcast_ref::<BinaryExpr>().unwrap();
 
         assert!(!is_cast_expr(left_binary.left()));
         assert!(!is_cast_expr(right_binary.left()));
@@ -525,17 +498,13 @@ mod tests {
         assert!(result.transformed);
 
         let optimized = result.data;
-        let optimized_binary = optimized.as_any().downcast_ref::<BinaryExpr>().unwrap();
+        let optimized_binary = optimized.downcast_ref::<BinaryExpr>().unwrap();
 
         // Verify the try_cast was removed
         assert!(!is_cast_expr(optimized_binary.left()));
 
         // Verify the literal was converted
-        let right_literal = optimized_binary
-            .right()
-            .as_any()
-            .downcast_ref::<Literal>()
-            .unwrap();
+        let right_literal = optimized_binary.right().downcast_ref::<Literal>().unwrap();
         assert_eq!(right_literal.value(), &ScalarValue::Int32(Some(100)));
     }
 
@@ -608,34 +577,18 @@ mod tests {
 
         // Verify both sides of the AND were optimized
         let optimized = result.data;
-        let and_binary = optimized.as_any().downcast_ref::<BinaryExpr>().unwrap();
+        let and_binary = optimized.downcast_ref::<BinaryExpr>().unwrap();
 
         // Left side should be: c1 > INT32(10)
-        let left_binary = and_binary
-            .left()
-            .as_any()
-            .downcast_ref::<BinaryExpr>()
-            .unwrap();
+        let left_binary = and_binary.left().downcast_ref::<BinaryExpr>().unwrap();
         assert!(!is_cast_expr(left_binary.left()));
-        let left_literal = left_binary
-            .right()
-            .as_any()
-            .downcast_ref::<Literal>()
-            .unwrap();
+        let left_literal = left_binary.right().downcast_ref::<Literal>().unwrap();
         assert_eq!(left_literal.value(), &ScalarValue::Int32(Some(10)));
 
         // Right side should be: c2 = INT64(20) (c2 is already INT64, literal cast to match)
-        let right_binary = and_binary
-            .right()
-            .as_any()
-            .downcast_ref::<BinaryExpr>()
-            .unwrap();
+        let right_binary = and_binary.right().downcast_ref::<BinaryExpr>().unwrap();
         assert!(!is_cast_expr(right_binary.left()));
-        let right_literal = right_binary
-            .right()
-            .as_any()
-            .downcast_ref::<Literal>()
-            .unwrap();
+        let right_literal = right_binary.right().downcast_ref::<Literal>().unwrap();
         assert_eq!(right_literal.value(), &ScalarValue::Int64(Some(20)));
     }
 }
diff --git a/datafusion/physical-expr/src/statistics/stats_solver.rs b/datafusion/physical-expr/src/statistics/stats_solver.rs
index 5665f7d1be..407fa6fd1f 100644
--- a/datafusion/physical-expr/src/statistics/stats_solver.rs
+++ b/datafusion/physical-expr/src/statistics/stats_solver.rs
@@ -86,7 +86,7 @@ impl ExprStatisticsGraphNode {
     /// indefinite range (i.e. `[-∞, ∞]`).
     pub fn make_node(node: &ExprTreeNode<NodeIndex>, schema: &Schema) -> Result<Self> {
         let expr = Arc::clone(&node.expr);
-        if let Some(literal) = expr.as_any().downcast_ref::<Literal>() {
+        if let Some(literal) = expr.downcast_ref::<Literal>() {
             let value = literal.value();
             Interval::try_new(value.clone(), value.clone())
                 .and_then(|interval| Self::new_uniform(expr, interval))
diff --git a/datafusion/physical-expr/src/utils/guarantee.rs b/datafusion/physical-expr/src/utils/guarantee.rs
index 00195a4df6..add7a34f70 100644
--- a/datafusion/physical-expr/src/utils/guarantee.rs
+++ b/datafusion/physical-expr/src/utils/guarantee.rs
@@ -126,9 +126,8 @@ impl LiteralGuarantee {
             .fold(GuaranteeBuilder::new(), |builder, expr| {
                 if let Some(cel) = ColOpLit::try_new(expr) {
                     builder.aggregate_conjunct(&cel)
-                } else if let Some(inlist) = expr
-                    .as_any()
-                    .downcast_ref::<crate::expressions::InListExpr>()
+                } else if let Some(inlist) =
+                    expr.downcast_ref::<crate::expressions::InListExpr>()
                 {
                     if let Some(inlist) = ColInList::try_new(inlist) {
                         builder.aggregate_multi_conjunct(
@@ -393,15 +392,10 @@ impl<'a> ColOpLit<'a> {
     ///
     /// Returns None otherwise
     fn try_new(expr: &'a Arc<dyn PhysicalExpr>) -> Option<Self> {
-        let binary_expr = expr
-            .as_any()
-            .downcast_ref::<crate::expressions::BinaryExpr>()?;
+        let binary_expr = expr.downcast_ref::<crate::expressions::BinaryExpr>()?;
 
-        let (left, op, right) = (
-            binary_expr.left().as_any(),
-            binary_expr.op(),
-            binary_expr.right().as_any(),
-        );
+        let (left, op, right) =
+            (binary_expr.left(), binary_expr.op(), binary_expr.right());
         let guarantee = match op {
             Operator::Eq => Guarantee::In,
             Operator::NotEq => Guarantee::NotIn,
@@ -449,15 +443,12 @@ impl<'a> ColInList<'a> {
     /// Returns None otherwise
     fn try_new(inlist: &'a crate::expressions::InListExpr) -> Option<Self> {
         // Only support single-column inlist currently, multi-column inlist is not supported
-        let col = inlist
-            .expr()
-            .as_any()
-            .downcast_ref::<crate::expressions::Column>()?;
+        let col = inlist.expr().downcast_ref::<crate::expressions::Column>()?;
 
         let literals = inlist
             .list()
             .iter()
-            .map(|e| e.as_any().downcast_ref::<crate::expressions::Literal>())
+            .map(|e| e.downcast_ref::<crate::expressions::Literal>())
             .collect::<Option<Vec<_>>>()?;
 
         let guarantee = if inlist.negated() {
@@ -482,10 +473,7 @@ enum ColOpLitOrInList<'a> {
 
 impl<'a> ColOpLitOrInList<'a> {
     fn try_new(expr: &'a Arc<dyn PhysicalExpr>) -> Option<Self> {
-        match expr
-            .as_any()
-            .downcast_ref::<crate::expressions::InListExpr>()
-        {
+        match expr.downcast_ref::<crate::expressions::InListExpr>() {
             Some(inlist) => Some(Self::ColInList(ColInList::try_new(inlist)?)),
             None => ColOpLit::try_new(expr).map(Self::ColOpLit),
         }
diff --git a/datafusion/physical-expr/src/utils/mod.rs b/datafusion/physical-expr/src/utils/mod.rs
index 6a8b49ac52..1be57c9192 100644
--- a/datafusion/physical-expr/src/utils/mod.rs
+++ b/datafusion/physical-expr/src/utils/mod.rs
@@ -71,15 +71,14 @@ impl ConstExpr {
             input_eqs: &EquivalenceProperties,
         ) -> Option<AcrossPartitions> {
             input_eqs.is_expr_constant(expr).or_else(|| {
-                expr.as_any()
-                    .downcast_ref::<Literal>()
+                expr.downcast_ref::<Literal>()
                     .map(|l| AcrossPartitions::Uniform(Some(l.value().clone())))
             })
         }
 
         let mut constants = Vec::new();
         for conjunction in split_conjunction(predicate) {
-            if let Some(binary) = conjunction.as_any().downcast_ref::<BinaryExpr>()
+            if let Some(binary) = conjunction.downcast_ref::<BinaryExpr>()
                 && binary.op() == &Operator::Eq
             {
                 // Check if either side is constant — either already known
@@ -145,7 +144,7 @@ fn split_impl<'a>(
     predicate: &'a Arc<dyn PhysicalExpr>,
     mut exprs: Vec<&'a Arc<dyn PhysicalExpr>>,
 ) -> Vec<&'a Arc<dyn PhysicalExpr>> {
-    match predicate.as_any().downcast_ref::<BinaryExpr>() {
+    match predicate.downcast_ref::<BinaryExpr>() {
         Some(binary) if binary.op() == &operator => {
             let exprs = split_impl(operator, binary.left(), exprs);
             split_impl(operator, binary.right(), exprs)
@@ -176,16 +175,14 @@ pub fn map_columns_before_projection(
     let column_mapping = proj_exprs
         .iter()
         .filter_map(|(expr, name)| {
-            expr.as_any()
-                .downcast_ref::<Column>()
+            expr.downcast_ref::<Column>()
                 .map(|column| (name.clone(), column.clone()))
         })
         .collect::<HashMap<_, _>>();
     parent_required
         .iter()
         .filter_map(|r| {
-            r.as_any()
-                .downcast_ref::<Column>()
+            r.downcast_ref::<Column>()
                 .and_then(|c| column_mapping.get(c.name()))
         })
         .map(|e| Arc::new(e.clone()) as _)
@@ -289,7 +286,7 @@ where
 pub fn collect_columns(expr: &Arc<dyn PhysicalExpr>) -> HashSet<Column> {
     let mut columns = HashSet::<Column>::new();
     expr.apply(|expr| {
-        if let Some(column) = expr.as_any().downcast_ref::<Column>() {
+        if let Some(column) = expr.downcast_ref::<Column>() {
             columns.get_or_insert_with(column, |c| c.clone());
         }
         Ok(TreeNodeRecursion::Continue)
@@ -313,7 +310,7 @@ pub fn reassign_expr_columns(
     schema: &Schema,
 ) -> Result<Arc<dyn PhysicalExpr>> {
     expr.transform_down(|expr| {
-        if let Some(column) = expr.as_any().downcast_ref::<Column>() {
+        if let Some(column) = expr.downcast_ref::<Column>() {
             let index = schema.index_of(column.name())?;
 
             return Ok(Transformed::yes(Arc::new(Column::new(
@@ -452,11 +449,11 @@ pub(crate) mod tests {
 
     fn make_dummy_node(node: &ExprTreeNode<NodeIndex>) -> Result<PhysicalExprDummyNode> {
         let expr = Arc::clone(&node.expr);
-        let dummy_property = if expr.as_any().is::<BinaryExpr>() {
+        let dummy_property = if expr.is::<BinaryExpr>() {
             "Binary"
-        } else if expr.as_any().is::<Column>() {
+        } else if expr.is::<Column>() {
             "Column"
-        } else if expr.as_any().is::<Literal>() {
+        } else if expr.is::<Literal>() {
             "Literal"
         } else {
             "Other"
diff --git a/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs b/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs
index 3677f3a45b..400161a94c 100644
--- a/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs
+++ b/datafusion/physical-optimizer/src/enforce_sorting/sort_pushdown.rs
@@ -430,7 +430,7 @@ fn handle_aggregate_pushdown(
     for req in parent_requirement {
         // Sort above AggregateExec should reference its output columns. Map each
         // output group-by column to its original input expression.
-        let Some(column) = req.expr.as_any().downcast_ref::<Column>() else {
+        let Some(column) = req.expr.downcast_ref::<Column>() else {
             return Ok(None);
         };
         if column.index() >= group_input_exprs.len() {
@@ -604,14 +604,13 @@ fn expr_source_side(
             let mut right_ordering = ordering.clone();
             let (mut valid_left, mut valid_right) = (true, true);
             for (left, right) in ordering.iter_mut().zip(right_ordering.iter_mut()) {
-                let col = left.expr.as_any().downcast_ref::<Column>()?;
+                let col = left.expr.downcast_ref::<Column>()?;
                 let eq_class = eq_group.get_equivalence_class(&left.expr);
                 if col.index() < left_columns_len {
                     if valid_right {
                         valid_right = eq_class.is_some_and(|cls| {
                             for expr in cls.iter() {
                                 if expr
-                                    .as_any()
                                     .downcast_ref::<Column>()
                                     .is_some_and(|c| c.index() >= left_columns_len)
                                 {
@@ -626,7 +625,6 @@ fn expr_source_side(
                     valid_left = eq_class.is_some_and(|cls| {
                         for expr in cls.iter() {
                             if expr
-                                .as_any()
                                 .downcast_ref::<Column>()
                                 .is_some_and(|c| c.index() < left_columns_len)
                             {
@@ -652,11 +650,11 @@ fn expr_source_side(
         }
         JoinType::LeftSemi | JoinType::LeftAnti => ordering
             .iter()
-            .all(|e| e.expr.as_any().is::<Column>())
+            .all(|e| e.expr.is::<Column>())
             .then_some((JoinSide::Left, ordering)),
         JoinType::RightSemi | JoinType::RightAnti => ordering
             .iter()
-            .all(|e| e.expr.as_any().is::<Column>())
+            .all(|e| e.expr.is::<Column>())
             .then_some((JoinSide::Right, ordering)),
     }
 }
@@ -739,7 +737,7 @@ fn handle_custom_pushdown(
                 let updated_columns = req
                     .expr
                     .transform_up(|expr| {
-                        if let Some(col) = expr.as_any().downcast_ref::<Column>() {
+                        if let Some(col) = expr.downcast_ref::<Column>() {
                             let new_index = col.index() - sub_offset;
                             Ok(Transformed::yes(Arc::new(Column::new(
                                 child_schema.field(new_index).name(),
@@ -822,7 +820,7 @@ fn handle_hash_join(
                 let updated_columns = req
                     .expr
                     .transform_up(|expr| {
-                        if let Some(col) = expr.as_any().downcast_ref::<Column>() {
+                        if let Some(col) = expr.downcast_ref::<Column>() {
                             let index = projected_indices[col.index()].index;
                             Ok(Transformed::yes(Arc::new(Column::new(
                                 child_schema.field(index).name(),
diff --git a/datafusion/physical-optimizer/src/projection_pushdown.rs b/datafusion/physical-optimizer/src/projection_pushdown.rs
index 7d8bf7aa47..fe71c21176 100644
--- a/datafusion/physical-optimizer/src/projection_pushdown.rs
+++ b/datafusion/physical-optimizer/src/projection_pushdown.rs
@@ -242,7 +242,7 @@ fn minimize_join_filter(
 ) -> JoinFilter {
     let mut used_columns = HashSet::new();
     expr.apply(|expr| {
-        if let Some(col) = expr.as_any().downcast_ref::<Column>() {
+        if let Some(col) = expr.downcast_ref::<Column>() {
             used_columns.insert(col.index());
         }
         Ok(TreeNodeRecursion::Continue)
@@ -265,7 +265,7 @@ fn minimize_join_filter(
         .collect::<Fields>();
 
     let final_expr = expr
-        .transform_up(|expr| match expr.as_any().downcast_ref::<Column>() {
+        .transform_up(|expr| match expr.downcast_ref::<Column>() {
             None => Ok(Transformed::no(expr)),
             Some(column) => {
                 let new_idx = used_columns
@@ -378,7 +378,7 @@ impl<'a> JoinFilterRewriter<'a> {
         // executed against the filter schema.
         let new_idx = self.join_side_projections.len();
         let rewritten_expr = expr.transform_up(|expr| {
-            Ok(match expr.as_any().downcast_ref::<Column>() {
+            Ok(match expr.downcast_ref::<Column>() {
                 None => Transformed::no(expr),
                 Some(column) => {
                     let intermediate_column =
@@ -412,7 +412,7 @@ impl<'a> JoinFilterRewriter<'a> {
         join_side: JoinSide,
     ) -> Result<bool> {
         let mut result = false;
-        expr.apply(|expr| match expr.as_any().downcast_ref::<Column>() {
+        expr.apply(|expr| match expr.downcast_ref::<Column>() {
             None => Ok(TreeNodeRecursion::Continue),
             Some(c) => {
                 let column_index = &self.intermediate_column_indices[c.index()];
diff --git a/datafusion/physical-optimizer/src/topk_aggregation.rs b/datafusion/physical-optimizer/src/topk_aggregation.rs
index 581edd86cd..e1779c04a6 100644
--- a/datafusion/physical-optimizer/src/topk_aggregation.rs
+++ b/datafusion/physical-optimizer/src/topk_aggregation.rs
@@ -100,7 +100,7 @@ impl TopKAggregation {
         let order = sort.properties().output_ordering()?;
         let order = order.iter().exactly_one().ok()?;
         let order_desc = order.options.descending;
-        let order = order.expr.as_any().downcast_ref::<Column>()?;
+        let order = order.expr.downcast_ref::<Column>()?;
         let mut cur_col_name = order.name().to_string();
         let limit = sort.fetch()?;
 
@@ -118,8 +118,7 @@ impl TopKAggregation {
             } else if let Some(proj) = plan.downcast_ref::<ProjectionExec>() {
                 // track renames due to successive projections
                 for proj_expr in proj.expr() {
-                    let Some(src_col) = proj_expr.expr.as_any().downcast_ref::<Column>()
-                    else {
+                    let Some(src_col) = proj_expr.expr.downcast_ref::<Column>() else {
                         continue;
                     };
                     if proj_expr.alias == cur_col_name {
diff --git a/datafusion/physical-plan/src/aggregates/mod.rs b/datafusion/physical-plan/src/aggregates/mod.rs
index 79a21d1c34..76ecb3f148 100644
--- a/datafusion/physical-plan/src/aggregates/mod.rs
+++ b/datafusion/physical-plan/src/aggregates/mod.rs
@@ -1006,7 +1006,7 @@ impl AggregateExec {
                 .iter()
                 .flat_map(|(_, target_cols)| {
                     target_cols.iter().flat_map(|(expr, _)| {
-                        expr.as_any().downcast_ref::<Column>().map(|c| c.index())
+                        expr.downcast_ref::<Column>().map(|c| c.index())
                     })
                 })
                 .collect(),
@@ -1098,7 +1098,7 @@ impl AggregateExec {
             let mut column_statistics = Statistics::unknown_column(&self.schema());
 
             for (idx, (expr, _)) in self.group_by.expr.iter().enumerate() {
-                if let Some(col) = expr.as_any().downcast_ref::<Column>() {
+                if let Some(col) = expr.downcast_ref::<Column>() {
                     let child_col_stats =
                         &child_statistics.column_statistics[col.index()];
                     column_statistics[idx].max_value = child_col_stats.max_value.clone();
@@ -1210,7 +1210,7 @@ impl AggregateExec {
                 if group_mask[j] {
                     continue;
                 }
-                let col = expr.as_any().downcast_ref::<Column>()?;
+                let col = expr.downcast_ref::<Column>()?;
                 let col_stats = &child_statistics.column_statistics[col.index()];
                 let ndv = *col_stats.distinct_count.get_value()?;
                 let null_adjustment = match col_stats.null_count.get_value() {
@@ -1265,7 +1265,7 @@ impl AggregateExec {
 
             // 2. arg should be only 1 column reference
             if let [arg] = aggr_expr.expressions().as_slice()
-                && arg.as_any().is::<Column>()
+                && arg.is::<Column>()
             {
                 all_cols.push(Arc::clone(arg));
                 aggr_dyn_filters.push(PerAccumulatorDynFilter {
diff --git a/datafusion/physical-plan/src/async_func.rs b/datafusion/physical-plan/src/async_func.rs
index 23f2b9ba1e..76a68bf570 100644
--- a/datafusion/physical-plan/src/async_func.rs
+++ b/datafusion/physical-plan/src/async_func.rs
@@ -338,8 +338,7 @@ impl AsyncMapper {
     ) -> Result<()> {
         // recursively look for references to async functions
         physical_expr.apply(|expr| {
-            if let Some(scalar_func_expr) =
-                expr.as_any().downcast_ref::<ScalarFunctionExpr>()
+            if let Some(scalar_func_expr) = expr.downcast_ref::<ScalarFunctionExpr>()
                 && scalar_func_expr.fun().as_async().is_some()
             {
                 let next_name = self.next_column_name();
diff --git a/datafusion/physical-plan/src/column_rewriter.rs b/datafusion/physical-plan/src/column_rewriter.rs
index c42bea90f0..2df95cd614 100644
--- a/datafusion/physical-plan/src/column_rewriter.rs
+++ b/datafusion/physical-plan/src/column_rewriter.rs
@@ -50,7 +50,7 @@ impl<'a> TreeNodeRewriter for PhysicalColumnRewriter<'a> {
         &mut self,
         node: Self::Node,
     ) -> datafusion_common::Result<Transformed<Self::Node>> {
-        if let Some(column) = node.as_any().downcast_ref::<Column>() {
+        if let Some(column) = node.downcast_ref::<Column>() {
             if let Some(new_column) = self.column_map.get(column) {
                 // jump to prevent rewriting the new sub-expression again
                 return Ok(Transformed::new(
diff --git a/datafusion/physical-plan/src/filter.rs b/datafusion/physical-plan/src/filter.rs
index 792f46daf9..0b3ddbf1de 100644
--- a/datafusion/physical-plan/src/filter.rs
+++ b/datafusion/physical-plan/src/filter.rs
@@ -1020,7 +1020,7 @@ fn collect_columns_from_predicate_inner(
 
     let predicates = split_conjunction(predicate);
     predicates.into_iter().for_each(|p| {
-        if let Some(binary) = p.as_any().downcast_ref::<BinaryExpr>() {
+        if let Some(binary) = p.downcast_ref::<BinaryExpr>() {
             // Only extract pairs where at least one side is a Column reference.
             // Pairs like `complex_expr = literal` should not create equivalence
             // classes — the literal could appear in many unrelated expressions
@@ -1029,8 +1029,8 @@ fn collect_columns_from_predicate_inner(
             // sort orderings. Constant propagation for such pairs is handled
             // separately by `extend_constants`.
             let has_direct_column_operand =
-                binary.left().as_any().downcast_ref::<Column>().is_some()
-                    || binary.right().as_any().downcast_ref::<Column>().is_some();
+                binary.left().downcast_ref::<Column>().is_some()
+                    || binary.right().downcast_ref::<Column>().is_some();
             if !has_direct_column_operand {
                 return;
             }
diff --git a/datafusion/physical-plan/src/filter_pushdown.rs b/datafusion/physical-plan/src/filter_pushdown.rs
index 7e82b9e823..810f9ffcbc 100644
--- a/datafusion/physical-plan/src/filter_pushdown.rs
+++ b/datafusion/physical-plan/src/filter_pushdown.rs
@@ -365,7 +365,7 @@ impl FilterRemapper {
     ) -> Result<Option<Arc<dyn PhysicalExpr>>> {
         let mut all_valid = true;
         let transformed = Arc::clone(filter).transform_down(|expr| {
-            if let Some(col) = expr.as_any().downcast_ref::<Column>() {
+            if let Some(col) = expr.downcast_ref::<Column>() {
                 if self.allowed_indices.contains(&col.index())
                     && let Ok(new_index) = self.child_schema.index_of(col.name())
                 {
diff --git a/datafusion/physical-plan/src/joins/hash_join/exec.rs b/datafusion/physical-plan/src/joins/hash_join/exec.rs
index d064f5ce6c..735375441f 100644
--- a/datafusion/physical-plan/src/joins/hash_join/exec.rs
+++ b/datafusion/physical-plan/src/joins/hash_join/exec.rs
@@ -1594,10 +1594,7 @@ impl ExecutionPlan for HashJoinExec {
                     .on
                     .iter()
                     .filter_map(|(left_key, _)| {
-                        left_key
-                            .as_any()
-                            .downcast_ref::<Column>()
-                            .map(|c| c.index())
+                        left_key.downcast_ref::<Column>().map(|c| c.index())
                     })
                     .collect();
                 for (output_idx, ci) in column_indices.iter().enumerate() {
@@ -1611,10 +1608,7 @@ impl ExecutionPlan for HashJoinExec {
                     .on
                     .iter()
                     .filter_map(|(_, right_key)| {
-                        right_key
-                            .as_any()
-                            .downcast_ref::<Column>()
-                            .map(|c| c.index())
+                        right_key.downcast_ref::<Column>().map(|c| c.index())
                     })
                     .collect();
                 for (output_idx, ci) in column_indices.iter().enumerate() {
diff --git a/datafusion/physical-plan/src/joins/hash_join/partitioned_hash_eval.rs b/datafusion/physical-plan/src/joins/hash_join/partitioned_hash_eval.rs
index f6c305fba6..0daac0bb86 100644
--- a/datafusion/physical-plan/src/joins/hash_join/partitioned_hash_eval.rs
+++ b/datafusion/physical-plan/src/joins/hash_join/partitioned_hash_eval.rs
@@ -17,7 +17,7 @@
 
 //! Hash computation and hash table lookup expressions for dynamic filtering
 
-use std::{any::Any, fmt::Display, hash::Hash, sync::Arc};
+use std::{fmt::Display, hash::Hash, sync::Arc};
 
 use arrow::{
     array::{ArrayRef, UInt64Array},
@@ -154,10 +154,6 @@ impl Display for HashExpr {
 }
 
 impl PhysicalExpr for HashExpr {
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     fn children(&self) -> Vec<&Arc<dyn PhysicalExpr>> {
         self.on_columns.iter().collect()
     }
@@ -300,10 +296,6 @@ impl Display for HashTableLookupExpr {
 }
 
 impl PhysicalExpr for HashTableLookupExpr {
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
     fn children(&self) -> Vec<&Arc<dyn PhysicalExpr>> {
         self.on_columns.iter().collect()
     }
diff --git a/datafusion/physical-plan/src/joins/stream_join_utils.rs b/datafusion/physical-plan/src/joins/stream_join_utils.rs
index d00b74df6b..571c199abb 100644
--- a/datafusion/physical-plan/src/joins/stream_join_utils.rs
+++ b/datafusion/physical-plan/src/joins/stream_join_utils.rs
@@ -377,7 +377,7 @@ fn convert_filter_columns(
     column_map: &HashMap<Column, Column>,
 ) -> Result<Option<Arc<dyn PhysicalExpr>>> {
     // Attempt to downcast the input expression to a Column type.
-    Ok(if let Some(col) = input.as_any().downcast_ref::<Column>() {
+    Ok(if let Some(col) = input.downcast_ref::<Column>() {
         // If the downcast is successful, retrieve the corresponding filter column.
         column_map.get(col).map(|c| Arc::new(c.clone()) as _)
     } else {
diff --git a/datafusion/physical-plan/src/joins/utils.rs b/datafusion/physical-plan/src/joins/utils.rs
index b0ecf7b789..a5ac890163 100644
--- a/datafusion/physical-plan/src/joins/utils.rs
+++ b/datafusion/physical-plan/src/joins/utils.rs
@@ -472,8 +472,8 @@ fn estimate_join_cardinality(
         .iter()
         .map(|(left, right)| {
             match (
-                left.as_any().downcast_ref::<Column>(),
-                right.as_any().downcast_ref::<Column>(),
+                left.downcast_ref::<Column>(),
+                right.downcast_ref::<Column>(),
             ) {
                 (Some(left), Some(right)) => (
                     left_stats.column_statistics[left.index()].clone(),
@@ -3009,7 +3009,6 @@ mod tests {
 
     fn assert_col_expr(expr: &Arc<dyn PhysicalExpr>, name: &str, index: usize) {
         let col = expr
-            .as_any()
             .downcast_ref::<Column>()
             .expect("Projection items should be Column expression");
         assert_eq!(col.name(), name);
diff --git a/datafusion/physical-plan/src/operator_statistics/mod.rs b/datafusion/physical-plan/src/operator_statistics/mod.rs
index ccb437a332..20266e9768 100644
--- a/datafusion/physical-plan/src/operator_statistics/mod.rs
+++ b/datafusion/physical-plan/src/operator_statistics/mod.rs
@@ -697,7 +697,7 @@ impl StatisticsProvider for AggregateStatisticsProvider {
         // Compute NDV product of GROUP BY columns
         let mut ndv_product: Option<usize> = None;
         for (expr, _) in agg.group_expr().expr().iter() {
-            let Some(col) = expr.as_any().downcast_ref::<Column>() else {
+            let Some(col) = expr.downcast_ref::<Column>() else {
                 return Ok(StatisticsResult::Delegate);
             };
             let Some(&ndv) = input_stats
@@ -800,12 +800,10 @@ impl StatisticsProvider for JoinStatisticsProvider {
             let mut ndv_divisor: usize = 1;
             for (left_key, right_key) in on {
                 let left_ndv = left_key
-                    .as_any()
                     .downcast_ref::<Column>()
                     .and_then(|c| left.column_statistics.get(c.index()))
                     .and_then(|s| s.distinct_count.get_value().copied());
                 let right_ndv = right_key
-                    .as_any()
                     .downcast_ref::<Column>()
                     .and_then(|c| right.column_statistics.get(c.index()))
                     .and_then(|s| s.distinct_count.get_value().copied());
diff --git a/datafusion/physical-plan/src/projection.rs b/datafusion/physical-plan/src/projection.rs
index 9cb4382e21..e5b91fbb1c 100644
--- a/datafusion/physical-plan/src/projection.rs
+++ b/datafusion/physical-plan/src/projection.rs
@@ -442,7 +442,7 @@ impl ExecutionPlan for ProjectionExec {
             // Recursively transform the expression
             let mut can_pushdown = true;
             let transformed = Arc::clone(&sort_expr.expr).transform(|expr| {
-                if let Some(col) = expr.as_any().downcast_ref::<Column>() {
+                if let Some(col) = expr.downcast_ref::<Column>() {
                     // Check if column index is valid.
                     // This should always be true but fail gracefully if it's not.
                     if col.index() >= self.expr().len() {
@@ -455,9 +455,7 @@ impl ExecutionPlan for ProjectionExec {
                     // Check if projection expression is a simple column
                     // We cannot push down order by clauses that depend on
                     // projected computations as they would have nothing to reference.
-                    if let Some(child_col) =
-                        proj_expr.expr.as_any().downcast_ref::<Column>()
-                    {
+                    if let Some(child_col) = proj_expr.expr.downcast_ref::<Column>() {
                         // Replace with the child column
                         Ok(Transformed::yes(Arc::new(child_col.clone()) as _))
                     } else {
@@ -750,7 +748,7 @@ pub fn remove_unnecessary_projections(
 fn is_projection_removable(projection: &ProjectionExec) -> bool {
     let exprs = projection.expr();
     exprs.iter().enumerate().all(|(idx, proj_expr)| {
-        let Some(col) = proj_expr.expr.as_any().downcast_ref::<Column>() else {
+        let Some(col) = proj_expr.expr.downcast_ref::<Column>() else {
             return false;
         };
         col.name() == proj_expr.alias && col.index() == idx
@@ -763,7 +761,6 @@ pub fn all_alias_free_columns(exprs: &[ProjectionExpr]) -> bool {
     exprs.iter().all(|proj_expr| {
         proj_expr
             .expr
-            .as_any()
             .downcast_ref::<Column>()
             .map(|column| column.name() == proj_expr.alias)
             .unwrap_or(false)
@@ -782,7 +779,6 @@ pub fn new_projections_for_columns(
         .filter_map(|proj_expr| {
             proj_expr
                 .expr
-                .as_any()
                 .downcast_ref::<Column>()
                 .map(|expr| source[expr.index()])
         })
@@ -801,9 +797,7 @@ pub fn make_with_child(
 
 /// Returns `true` if all the expressions in the argument are `Column`s.
 pub fn all_columns(exprs: &[ProjectionExpr]) -> bool {
-    exprs
-        .iter()
-        .all(|proj_expr| proj_expr.expr.as_any().is::<Column>())
+    exprs.iter().all(|proj_expr| proj_expr.expr.is::<Column>())
 }
 
 /// Updates the given lexicographic ordering according to given projected
@@ -852,7 +846,6 @@ pub fn physical_to_column_exprs(
         .map(|proj_expr| {
             proj_expr
                 .expr
-                .as_any()
                 .downcast_ref::<Column>()
                 .map(|col| (col.clone(), proj_expr.alias.clone()))
         })
@@ -1035,7 +1028,7 @@ fn try_unifying_projections(
             .expr
             .apply(|expr| {
                 Ok({
-                    if let Some(column) = expr.as_any().downcast_ref::<Column>() {
+                    if let Some(column) = expr.downcast_ref::<Column>() {
                         *column_ref_map.entry(column.clone()).or_default() += 1;
                     }
                     TreeNodeRecursion::Continue
@@ -1083,7 +1076,7 @@ fn collect_column_indices(exprs: &[ProjectionExpr]) -> Vec<usize> {
     let mut seen = std::collections::HashSet::new();
     let mut indices = Vec::new();
     for proj_expr in exprs {
-        if let Some(col) = proj_expr.expr.as_any().downcast_ref::<Column>() {
+        if let Some(col) = proj_expr.expr.downcast_ref::<Column>() {
             // Simple column reference: preserve projection order.
             if seen.insert(col.index()) {
                 indices.push(col.index());
@@ -1095,7 +1088,7 @@ fn collect_column_indices(exprs: &[ProjectionExpr]) -> Vec<usize> {
             proj_expr
                 .expr
                 .apply(|expr| {
-                    if let Some(col) = expr.as_any().downcast_ref::<Column>()
+                    if let Some(col) = expr.downcast_ref::<Column>()
                         && seen.insert(col.index())
                     {
                         indices.push(col.index());
@@ -1151,7 +1144,7 @@ fn new_columns_for_join_on(
             // Rewrite all columns in `on`
             Arc::clone(*on)
                 .transform(|expr| {
-                    if let Some(column) = expr.as_any().downcast_ref::<Column>() {
+                    if let Some(column) = expr.downcast_ref::<Column>() {
                         // Find the column in the projection expressions
                         let new_column = projection_exprs
                             .iter()
diff --git a/datafusion/proto/src/physical_plan/to_proto.rs b/datafusion/proto/src/physical_plan/to_proto.rs
index 990a54cf94..f8665df339 100644
--- a/datafusion/proto/src/physical_plan/to_proto.rs
+++ b/datafusion/proto/src/physical_plan/to_proto.rs
@@ -259,8 +259,7 @@ pub fn serialize_physical_expr_with_converter(
 ) -> Result<protobuf::PhysicalExprNode> {
     // Snapshot the expr in case it has dynamic predicate state so
     // it can be serialized
-    let value = snapshot_physical_expr(Arc::clone(value))?;
-    let expr = value.as_any();
+    let expr = snapshot_physical_expr(Arc::clone(value))?;
 
     // HashTableLookupExpr is used for dynamic filter pushdown in hash joins.
     // It contains an Arc<dyn JoinHashMapType> (the build-side hash table) which
@@ -506,7 +505,7 @@ pub fn serialize_physical_expr_with_converter(
         })
     } else {
         let mut buf: Vec<u8> = vec![];
-        match codec.try_encode_expr(&value, &mut buf) {
+        match codec.try_encode_expr(value, &mut buf) {
             Ok(_) => {
                 let inputs: Vec<protobuf::PhysicalExprNode> = value
                     .children()
diff --git a/datafusion/proto/tests/cases/roundtrip_physical_plan.rs b/datafusion/proto/tests/cases/roundtrip_physical_plan.rs
index 15639bcd25..58b2771785 100644
--- a/datafusion/proto/tests/cases/roundtrip_physical_plan.rs
+++ b/datafusion/proto/tests/cases/roundtrip_physical_plan.rs
@@ -15,7 +15,6 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use std::any::Any;
 use std::collections::HashMap;
 use std::fmt::{Display, Formatter};
 use std::sync::{Arc, RwLock};
@@ -1100,10 +1099,6 @@ fn roundtrip_parquet_exec_with_custom_predicate_expr() -> Result<()> {
     }
 
     impl PhysicalExpr for CustomPredicateExpr {
-        fn as_any(&self) -> &dyn Any {
-            self
-        }
-
         fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
             unreachable!()
         }
@@ -1171,11 +1166,7 @@ fn roundtrip_parquet_exec_with_custom_predicate_expr() -> Result<()> {
             node: &Arc<dyn PhysicalExpr>,
             buf: &mut Vec<u8>,
         ) -> Result<()> {
-            if node
-                .as_any()
-                .downcast_ref::<CustomPredicateExpr>()
-                .is_some()
-            {
+            if node.downcast_ref::<CustomPredicateExpr>().is_some() {
                 buf.extend_from_slice("CustomPredicateExpr".as_bytes());
                 Ok(())
             } else {
@@ -2519,7 +2510,7 @@ fn roundtrip_hash_table_lookup_expr_to_lit() -> Result<()> {
     // Verify the filter predicate is a Literal(true)
     let result_filter = result.downcast_ref::<FilterExec>().unwrap();
     let predicate = result_filter.predicate();
-    let literal = predicate.as_any().downcast_ref::<Literal>().unwrap();
+    let literal = predicate.downcast_ref::<Literal>().unwrap();
     assert_eq!(*literal.value(), ScalarValue::Boolean(Some(true)));
 
     Ok(())
@@ -2862,10 +2853,7 @@ fn test_backward_compatibility_no_expr_id() -> Result<()> {
     )?;
 
     // Verify the result is correct
-    let col = result
-        .as_any()
-        .downcast_ref::<Column>()
-        .expect("Expected Column");
+    let col = result.downcast_ref::<Column>().expect("Expected Column");
     assert_eq!(col.name(), "a");
     assert_eq!(col.index(), 0);
 
@@ -2996,7 +2984,6 @@ fn test_deduplication_within_expr_deserialization() -> Result<()> {
 
     // Check that deduplication worked within the deserialization
     let binary1 = expr1
-        .as_any()
         .downcast_ref::<BinaryExpr>()
         .expect("Expected BinaryExpr");
     assert!(
@@ -3014,7 +3001,6 @@ fn test_deduplication_within_expr_deserialization() -> Result<()> {
 
     // Check that the second expression was also deserialized correctly
     let binary2 = expr2
-        .as_any()
         .downcast_ref::<BinaryExpr>()
         .expect("Expected BinaryExpr");
     assert!(
diff --git a/datafusion/pruning/src/pruning_predicate.rs b/datafusion/pruning/src/pruning_predicate.rs
index 8ca8264fe3..76cf14be88 100644
--- a/datafusion/pruning/src/pruning_predicate.rs
+++ b/datafusion/pruning/src/pruning_predicate.rs
@@ -695,15 +695,13 @@ impl BoolVecBuilder {
 }
 
 fn is_always_true(expr: &Arc<dyn PhysicalExpr>) -> bool {
-    expr.as_any()
-        .downcast_ref::<phys_expr::Literal>()
+    expr.downcast_ref::<phys_expr::Literal>()
         .map(|l| matches!(l.value(), ScalarValue::Boolean(Some(true))))
         .unwrap_or_default()
 }
 
 fn is_always_false(expr: &Arc<dyn PhysicalExpr>) -> bool {
-    expr.as_any()
-        .downcast_ref::<phys_expr::Literal>()
+    expr.downcast_ref::<phys_expr::Literal>()
         .map(|l| matches!(l.value(), ScalarValue::Boolean(Some(false))))
         .unwrap_or_default()
 }
@@ -1113,15 +1111,10 @@ fn rewrite_expr_to_prunable(
         return plan_err!("rewrite_expr_to_prunable only support compare expression");
     }
 
-    let column_expr_any = column_expr.as_any();
-
-    if column_expr_any
-        .downcast_ref::<phys_expr::Column>()
-        .is_some()
-    {
+    if column_expr.downcast_ref::<phys_expr::Column>().is_some() {
         // `col op lit()`
         Ok((Arc::clone(column_expr), op, Arc::clone(scalar_expr)))
-    } else if let Some(cast) = column_expr_any.downcast_ref::<phys_expr::CastExpr>() {
+    } else if let Some(cast) = column_expr.downcast_ref::<phys_expr::CastExpr>() {
         // `cast(col) op lit()`
         let (left, op, right) = rewrite_cast_child_to_prunable(
             cast.expr(),
@@ -1140,9 +1133,7 @@ fn rewrite_expr_to_prunable(
         // extraction to agree with PruningPredicate on a stats representation
         // for nested field expressions.
         Ok((left, op, right))
-    } else if let Some(try_cast) =
-        column_expr_any.downcast_ref::<phys_expr::TryCastExpr>()
-    {
+    } else if let Some(try_cast) = column_expr.downcast_ref::<phys_expr::TryCastExpr>() {
         // `try_cast(col) op lit()`
         let (left, op, right) = rewrite_cast_child_to_prunable(
             try_cast.expr(),
@@ -1156,23 +1147,18 @@ fn rewrite_expr_to_prunable(
             try_cast.cast_type().clone(),
         ));
         Ok((left, op, right))
-    } else if let Some(neg) = column_expr_any.downcast_ref::<phys_expr::NegativeExpr>() {
+    } else if let Some(neg) = column_expr.downcast_ref::<phys_expr::NegativeExpr>() {
         // `-col > lit()`  --> `col < -lit()`
         let (left, op, right) =
             rewrite_expr_to_prunable(neg.arg(), op, scalar_expr, schema)?;
         let right = Arc::new(phys_expr::NegativeExpr::new(right));
         Ok((left, reverse_operator(op)?, right))
-    } else if let Some(not) = column_expr_any.downcast_ref::<phys_expr::NotExpr>() {
+    } else if let Some(not) = column_expr.downcast_ref::<phys_expr::NotExpr>() {
         // `!col = true` --> `col = !true`
         if op != Operator::Eq && op != Operator::NotEq {
             return plan_err!("Not with operator other than Eq / NotEq is not supported");
         }
-        if not
-            .arg()
-            .as_any()
-            .downcast_ref::<phys_expr::Column>()
-            .is_some()
-        {
+        if not.arg().downcast_ref::<phys_expr::Column>().is_some() {
             let left = Arc::clone(not.arg());
             let right = Arc::new(phys_expr::NotExpr::new(Arc::clone(scalar_expr)));
             Ok((left, reverse_operator(op)?, right))
@@ -1249,7 +1235,7 @@ fn rewrite_column_expr(
     column_new: &phys_expr::Column,
 ) -> Result<Arc<dyn PhysicalExpr>> {
     e.transform(|expr| {
-        if let Some(column) = expr.as_any().downcast_ref::<phys_expr::Column>()
+        if let Some(column) = expr.downcast_ref::<phys_expr::Column>()
             && column == column_old
         {
             return Ok(Transformed::yes(Arc::new(column_new.clone())));
@@ -1323,7 +1309,7 @@ fn build_is_null_column_expr(
     required_columns: &mut RequiredColumns,
     with_not: bool,
 ) -> Option<Arc<dyn PhysicalExpr>> {
-    if let Some(col) = expr.as_any().downcast_ref::<phys_expr::Column>() {
+    if let Some(col) = expr.downcast_ref::<phys_expr::Column>() {
         let field = schema.field_with_name(col.name()).ok()?;
 
         let null_count_field = &Field::new(field.name(), DataType::UInt64, true);
@@ -1440,12 +1426,11 @@ fn build_predicate_expression(
         return Arc::clone(expr);
     }
     // predicate expression can only be a binary expression
-    let expr_any = expr.as_any();
-    if let Some(is_null) = expr_any.downcast_ref::<phys_expr::IsNullExpr>() {
+    if let Some(is_null) = expr.downcast_ref::<phys_expr::IsNullExpr>() {
         return build_is_null_column_expr(is_null.arg(), schema, required_columns, false)
             .unwrap_or_else(|| unhandled_hook.handle(expr));
     }
-    if let Some(is_not_null) = expr_any.downcast_ref::<phys_expr::IsNotNullExpr>() {
+    if let Some(is_not_null) = expr.downcast_ref::<phys_expr::IsNotNullExpr>() {
         return build_is_null_column_expr(
             is_not_null.arg(),
             schema,
@@ -1454,20 +1439,20 @@ fn build_predicate_expression(
         )
         .unwrap_or_else(|| unhandled_hook.handle(expr));
     }
-    if let Some(col) = expr_any.downcast_ref::<phys_expr::Column>() {
+    if let Some(col) = expr.downcast_ref::<phys_expr::Column>() {
         return build_single_column_expr(col, schema, required_columns, false)
             .unwrap_or_else(|| unhandled_hook.handle(expr));
     }
-    if let Some(not) = expr_any.downcast_ref::<phys_expr::NotExpr>() {
+    if let Some(not) = expr.downcast_ref::<phys_expr::NotExpr>() {
         // match !col (don't do so recursively)
-        if let Some(col) = not.arg().as_any().downcast_ref::<phys_expr::Column>() {
+        if let Some(col) = not.arg().downcast_ref::<phys_expr::Column>() {
             return build_single_column_expr(col, schema, required_columns, true)
                 .unwrap_or_else(|| unhandled_hook.handle(expr));
         } else {
             return unhandled_hook.handle(expr);
         }
     }
-    if let Some(in_list) = expr_any.downcast_ref::<phys_expr::InListExpr>() {
+    if let Some(in_list) = expr.downcast_ref::<phys_expr::InListExpr>() {
         if !in_list.list().is_empty()
             && in_list.list().len() <= MAX_LIST_VALUE_SIZE_REWRITE
         {
@@ -1505,13 +1490,13 @@ fn build_predicate_expression(
     }
 
     let (left, op, right) = {
-        if let Some(bin_expr) = expr_any.downcast_ref::<phys_expr::BinaryExpr>() {
+        if let Some(bin_expr) = expr.downcast_ref::<phys_expr::BinaryExpr>() {
             (
                 Arc::clone(bin_expr.left()),
                 *bin_expr.op(),
                 Arc::clone(bin_expr.right()),
             )
-        } else if let Some(like_expr) = expr_any.downcast_ref::<phys_expr::LikeExpr>() {
+        } else if let Some(like_expr) = expr.downcast_ref::<phys_expr::LikeExpr>() {
             if like_expr.case_insensitive() {
                 return unhandled_hook.handle(expr);
             }
@@ -1607,7 +1592,7 @@ impl ColumnReferenceCount {
     fn from_expression(expr: &Arc<dyn PhysicalExpr>) -> Self {
         let mut seen = HashSet::<phys_expr::Column>::new();
         expr.apply(|expr| {
-            if let Some(column) = expr.as_any().downcast_ref::<phys_expr::Column>() {
+            if let Some(column) = expr.downcast_ref::<phys_expr::Column>() {
                 seen.insert(column.clone());
                 if seen.len() > 1 {
                     return Ok(TreeNodeRecursion::Stop);
@@ -1725,7 +1710,7 @@ fn unpack_string(s: &ScalarValue) -> Option<&str> {
 }
 
 fn extract_string_literal(expr: &Arc<dyn PhysicalExpr>) -> Option<&str> {
-    if let Some(lit) = expr.as_any().downcast_ref::<phys_expr::Literal>() {
+    if let Some(lit) = expr.downcast_ref::<phys_expr::Literal>() {
         let s = unpack_string(lit.value())?;
         return Some(s);
     }
@@ -2994,8 +2979,7 @@ mod tests {
             .children()
             .into_iter()
             .map(|child_expr| {
-                let Some(col_expr) =
-                    child_expr.as_any().downcast_ref::<phys_expr::Column>()
+                let Some(col_expr) = child_expr.downcast_ref::<phys_expr::Column>()
                 else {
                     return Arc::clone(child_expr);
                 };
diff --git a/docs/source/library-user-guide/upgrading/54.0.0.md b/docs/source/library-user-guide/upgrading/54.0.0.md
index 16606a0f40..da1db6de32 100644
--- a/docs/source/library-user-guide/upgrading/54.0.0.md
+++ b/docs/source/library-user-guide/upgrading/54.0.0.md
@@ -201,89 +201,20 @@ let mut stats = Arc::unwrap_or_clone(plan.partition_statistics(None)?);
 stats.column_statistics[0].min_value = ...;
 ```
 
-### Remove `as_any` from `ScalarUDFImpl`, `AggregateUDFImpl`, `WindowUDFImpl`, `ExecutionPlan`, `TableProvider`, `SchemaProvider`, `CatalogProvider`, and `CatalogProviderList`
+### Remove `as_any` from `PhysicalExpr`, `ScalarUDFImpl`, `AggregateUDFImpl`, `WindowUDFImpl`, `ExecutionPlan`, `TableProvider`, `SchemaProvider`, `CatalogProvider`, and `CatalogProviderList`
 
 Now that we have a more recent minimum version of Rust, we can take advantage of
 trait upcasting. This reduces the amount of boilerplate code that
-users need to implement. In your implementations of `ScalarUDFImpl`,
-`AggregateUDFImpl`, `WindowUDFImpl`, `ExecutionPlan`, `TableProvider`,
-`SchemaProvider`, `CatalogProvider`, and `CatalogProviderList`, you can simply
-remove the `as_any` function. The below diffs are examples from the associated
-PRs.
-
-**Scalar UDFs:**
-
-```diff
- impl ScalarUDFImpl for MyEq {
--    fn as_any(&self) -> &dyn Any {
--        self
--    }
--
-     fn name(&self) -> &str {
-         "my_eq"
-     }
-
-     ...
- }
-```
-
-**Aggregate UDFs:**
-
-```diff
- impl AggregateUDFImpl for GeoMeanUdf {
--    fn as_any(&self) -> &dyn Any {
--        self
--    }
--
-     fn name(&self) -> &str {
-         "geo_mean"
-     }
-
-     ...
- }
-```
-
-**Window UDFs:**
-
-```diff
- impl WindowUDFImpl for SmoothIt {
--    fn as_any(&self) -> &dyn Any {
--        self
--    }
--
-     fn name(&self) -> &str {
-         "smooth_it"
-     }
-
-     ...
- }
-```
-
-**Execution Plans:**
-
-```diff
- impl ExecutionPlan for MyExec {
--    fn as_any(&self) -> &dyn Any {
--        self
--    }
--
-     fn name(&self) -> &'static str {
-         "MyExec"
-     }
-
-     ...
- }
-```
-
-**Table Providers:**
+users need to implement. In your implementations of the traits listed above,
+you can simply remove the `as_any` function. For example:
 
 ```diff
- impl TableProvider for MyTable {
+ impl PhysicalExpr for MyExpr {
 -    fn as_any(&self) -> &dyn Any {
 -        self
 -    }
 -
-     fn schema(&self) -> SchemaRef {
+     fn data_type(&self, input_schema: &Schema) -> Result<DataType> {
          ...
      }
 
@@ -291,79 +222,19 @@ PRs.
  }
 ```
 
-**Schema Providers:**
-
-```diff
- impl SchemaProvider for MySchema {
--    fn as_any(&self) -> &dyn Any {
--        self
--    }
--
-     fn table_names(&self) -> Vec<String> {
-         ...
-     }
-
-     ...
- }
-```
+The same change applies to all of the above traits — simply delete the
+`as_any` method from each implementation.
 
-**Catalog Providers:**
+If you have code that is downcasting, you can drop the `.as_any()` call
+and use `downcast_ref` / `is` directly on the trait object:
 
 ```diff
- impl CatalogProvider for MyCatalog {
--    fn as_any(&self) -> &dyn Any {
--        self
--    }
--
-     fn schema_names(&self) -> Vec<String> {
-         ...
-     }
-
-     ...
- }
-```
-
-**Catalog Provider Lists:**
-
-```diff
- impl CatalogProviderList for MyCatalogList {
--    fn as_any(&self) -> &dyn Any {
--        self
--    }
--
-     fn register_catalog(
-         ...
-     }
-
-     ...
- }
-```
-
-If you have code that is downcasting, you can use the new `downcast_ref`
-and `is` methods defined directly on each trait object:
-
-**Before:**
-
-```rust,ignore
-let exec = plan.as_any().downcast_ref::<MyExec>().unwrap();
-let udf = scalar_udf.as_any().downcast_ref::<MyUdf>().unwrap();
-let table = table_provider.as_any().downcast_ref::<MyTable>().unwrap();
-```
-
-**After:**
-
-```rust,ignore
-let exec = plan.downcast_ref::<MyExec>().unwrap();
-let udf = scalar_udf.downcast_ref::<MyUdf>().unwrap();
-let table = table_provider.downcast_ref::<MyTable>().unwrap();
+-let exec = plan.as_any().downcast_ref::<MyExec>().unwrap();
++let exec = plan.downcast_ref::<MyExec>().unwrap();
 ```
 
-These methods are available on `dyn ExecutionPlan`, `dyn TableProvider`,
-`dyn SchemaProvider`, `dyn CatalogProvider`, `dyn CatalogProviderList`,
-`dyn ScalarUDFImpl`, `dyn AggregateUDFImpl`, and `dyn WindowUDFImpl`.
-They work correctly
-whether the value is a bare reference or behind an `Arc` (Rust
-auto-derefs through the `Arc`).
+These methods work correctly whether the value is a bare reference or
+behind an `Arc` (Rust auto-derefs through the `Arc`).
 
 > **Warning:** Do not cast an `Arc<dyn Trait>` directly to `&dyn Any`.
 > Writing `(&plan as &dyn Any)` gives you an `Any` reference to the


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org