Repository: MaterializeInc/materialize
Pull request #32487: optimizer: Typecheck also in local optimization
Author: login: ggevay
Labels: name: A-optimization
description: Area: query optimization and transformation
name: A-CLUSTER
description: Topics related to the CLUSTER layer
Created: 2025-05-14T09:41:25Z | Merged: 2025-05-15T14:32:55Z

This PR makes us do typechecking also in local optimization. Not doing this earlier was the reason that we did not catch https://github.com/MaterializeInc/materialize/pull/32486.

(Normally, `optimize_dataflow_relations` sets the `GlobalId` in the `TransformCtx`, but `optimize_dataflow_relations` is not involved in the local optimization.)

This should now produce errors in `list.slt` (because https://github.com/MaterializeInc/materialize/pull/32486 is not fixed on this branch), but I'm not sure if this will fail CI. Running CI with the PR in draft to find out. I'm also running all slts locally with an explicit panic instead of the error, and with https://github.com/MaterializeInc/materialize/pull/32486 cherry-picked, to see if any other type errors show up due to doing typechecking in more places now.

Edit: There are also type errors in `src/transform/tests/test_runner.rs`! Need to investigate! Edit 2: Found it, will fix it up after the meeting.

### Motivation

<!--
Which of the following best describes the motivation behind this PR?

  * This PR fixes a recognized bug.

    [Ensure issue is linked somewhere.]

  * This PR adds a known-desirable feature.

    [Ensure issue is linked somewhere.]

  * This PR fixes a previously unreported bug.

    [Describe the bug in detail, as if you were filing a bug report.]

  * This PR adds a feature that has not yet been specified.

    [Write a brief specification for the feature, including justification
     for its inclusion in Materialize, as if you were writing the original
     feature specification.]

   * This PR refactors existing code.

    [Describe what was wrong with the existing code, if it is not obvious.]
-->

### Tips for reviewer

<!--
Leave some tips for your reviewer, like:

    * The diff is much smaller if viewed with whitespace hidden.
    * [Some function/module/file] deserves extra attention.
    * [Some function/module/file] is pure code movement and only needs a skim.

Delete this section if no tips.
-->

### Checklist

- [ ] This PR has adequate test coverage / QA involvement has been duly considered. ([trigger-ci for additional test/nightly runs](https://trigger-ci.dev.materialize.com/))
- [ ] This PR has an associated up-to-date [design doc](https://github.com/MaterializeInc/materialize/blob/main/doc/developer/design/README.md), is a design doc ([template](https://github.com/MaterializeInc/materialize/blob/main/doc/developer/design/00000000_template.md)), or is sufficiently small to not require a design.
  <!-- Reference the design in the description. -->
- [ ] If this PR evolves [an existing `$T ⇔ Proto$T` mapping](https://github.com/MaterializeInc/materialize/blob/main/doc/developer/command-and-response-binary-encoding.md) (possibly in a backwards-incompatible way), then it is tagged with a `T-proto` label.
- [ ] If this PR will require changes to cloud orchestration or tests, there is a companion cloud PR to account for those changes that is tagged with the release-blocker label ([example](https://github.com/MaterializeInc/cloud/pull/5021)).
  <!-- Ask in #team-cloud on Slack if you need help preparing the cloud PR. -->
- [ ] If this PR includes major [user-facing behavior changes](https://github.com/MaterializeInc/materialize/blob/main/doc/developer/guide-changes.md#what-changes-require-a-release-note), I have pinged the relevant PM to schedule a changelog post.



Diff:
diff --git a/src/adapter/src/optimize/copy_to.rs b/src/adapter/src/optimize/copy_to.rs
index d5c7a332f4135..9ef74e90ba09d 100644
--- a/src/adapter/src/optimize/copy_to.rs
+++ b/src/adapter/src/optimize/copy_to.rs
@@ -169,6 +169,7 @@ impl Optimize<HirRelationExpr> for Optimizer {
             &self.typecheck_ctx,
             &mut df_meta,
             Some(&self.metrics),
+            Some(self.select_id),
         );
         let expr = optimize_mir_local(expr, &mut transform_ctx)?.into_inner();
 
diff --git a/src/adapter/src/optimize/materialized_view.rs b/src/adapter/src/optimize/materialized_view.rs
index 7d0b0ad6520c7..18b78a5daa4f3 100644
--- a/src/adapter/src/optimize/materialized_view.rs
+++ b/src/adapter/src/optimize/materialized_view.rs
@@ -199,6 +199,7 @@ impl Optimize<HirRelationExpr> for Optimizer {
             &self.typecheck_ctx,
             &mut df_meta,
             Some(&self.metrics),
+            Some(self.view_id),
         );
         let expr = optimize_mir_local(expr, &mut transform_ctx)?.into_inner();
 
diff --git a/src/adapter/src/optimize/peek.rs b/src/adapter/src/optimize/peek.rs
index f34eee3b065c4..a80b062a2c4e1 100644
--- a/src/adapter/src/optimize/peek.rs
+++ b/src/adapter/src/optimize/peek.rs
@@ -183,6 +183,7 @@ impl Optimize<HirRelationExpr> for Optimizer {
             &self.typecheck_ctx,
             &mut df_meta,
             Some(&self.metrics),
+            Some(self.select_id),
         );
         let expr = optimize_mir_local(expr, &mut transform_ctx)?.into_inner();
 
diff --git a/src/adapter/src/optimize/subscribe.rs b/src/adapter/src/optimize/subscribe.rs
index 5bde0a5cb1c47..1c4449d144a05 100644
--- a/src/adapter/src/optimize/subscribe.rs
+++ b/src/adapter/src/optimize/subscribe.rs
@@ -231,6 +231,7 @@ impl Optimize<SubscribeFrom> for Optimizer {
                     &self.typecheck_ctx,
                     &mut df_meta,
                     Some(&self.metrics),
+                    Some(self.view_id),
                 );
                 let expr = optimize_mir_local(expr, &mut transform_ctx)?;
 
diff --git a/src/adapter/src/optimize/view.rs b/src/adapter/src/optimize/view.rs
index 19a47d62527a8..c136d37772f1e 100644
--- a/src/adapter/src/optimize/view.rs
+++ b/src/adapter/src/optimize/view.rs
@@ -69,6 +69,7 @@ impl Optimize<HirRelationExpr> for Optimizer {
             &self.typecheck_ctx,
             &mut df_meta,
             self.metrics.as_ref(),
+            None,
         );
 
         // First, we run a very simple optimizer pipeline, which only folds constants. This takes
diff --git a/src/transform/src/fusion/filter.rs b/src/transform/src/fusion/filter.rs
index 761b64298533b..83d8fd0330acb 100644
--- a/src/transform/src/fusion/filter.rs
+++ b/src/transform/src/fusion/filter.rs
@@ -37,7 +37,7 @@
 //! let features = OptimizerFeatures::default();
 //! let typecheck_ctx = typecheck::empty_context();
 //! let mut df_meta = DataflowMetainfo::default();
-//! let mut transform_ctx = TransformCtx::local(&features, &typecheck_ctx, &mut df_meta, None);
+//! let mut transform_ctx = TransformCtx::local(&features, &typecheck_ctx, &mut df_meta, None, None);
 //!
 //! // Filter.transform() will deduplicate any predicates
 //! Filter.transform(&mut expr, &mut transform_ctx);
diff --git a/src/transform/src/lib.rs b/src/transform/src/lib.rs
index 1016c541a5d8d..7533f05c06335 100644
--- a/src/transform/src/lib.rs
+++ b/src/transform/src/lib.rs
@@ -144,11 +144,12 @@ impl<'a> TransformCtx<'a> {
         typecheck_ctx: &'a SharedContext,
         df_meta: &'a mut DataflowMetainfo,
         metrics: Option<&'a OptimizerMetrics>,
+        global_id: Option<GlobalId>,
     ) -> Self {
         Self {
             indexes: &EmptyIndexOracle,
             stats: &EmptyStatisticsOracle,
-            global_id: None,
+            global_id,
             features,
             typecheck_ctx,
             df_meta,
@@ -185,7 +186,8 @@ impl<'a> TransformCtx<'a> {
         Arc::clone(self.typecheck_ctx)
     }
 
-    fn set_global_id(&mut self, global_id: GlobalId) {
+    /// Lets self know the id of the object that is being optimized.
+    pub fn set_global_id(&mut self, global_id: GlobalId) {
         self.global_id = Some(global_id);
     }
 
diff --git a/src/transform/src/predicate_pushdown.rs b/src/transform/src/predicate_pushdown.rs
index f517acc56a6af..2ddd2c0011dd3 100644
--- a/src/transform/src/predicate_pushdown.rs
+++ b/src/transform/src/predicate_pushdown.rs
@@ -66,7 +66,7 @@
 //! let features = OptimizerFeatures::default();
 //! let typecheck_ctx = typecheck::empty_context();
 //! let mut df_meta = DataflowMetainfo::default();
-//! let mut transform_ctx = TransformCtx::local(&features, &typecheck_ctx, &mut df_meta, None);
+//! let mut transform_ctx = TransformCtx::local(&features, &typecheck_ctx, &mut df_meta, None, None);
 //!
 //! PredicatePushdown::default().transform(&mut expr, &mut transform_ctx);
 //!
diff --git a/src/transform/tests/test_runner.rs b/src/transform/tests/test_runner.rs
index ead6c826fad3a..699a7c3066799 100644
--- a/src/transform/tests/test_runner.rs
+++ b/src/transform/tests/test_runner.rs
@@ -46,27 +46,33 @@ mod tests {
     const JSON: &str = "json";
     const TEST: &str = "test";
 
-    thread_local! {
-        static FULL_TRANSFORM_LIST: Vec<Box<dyn Transform>> = {
-            let features = OptimizerFeatures::default();
-            let typecheck_ctx = typecheck::empty_context();
-            let mut df_meta = DataflowMetainfo::default();
-            let mut transform_ctx = TransformCtx::local(&features, &typecheck_ctx, &mut df_meta, None);
+    const TEST_GLOBAL_ID: GlobalId = GlobalId::Transient(1234567);
 
-            #[allow(deprecated)]
-            Optimizer::logical_optimizer(&mut transform_ctx)
-                .transforms
-                .into_iter()
-                .chain(std::iter::once::<Box<dyn Transform>>(
-                    Box::new(mz_transform::movement::ProjectionPushdown::default())
-                ))
-                .chain(std::iter::once::<Box<dyn Transform>>(
-                    Box::new(mz_transform::normalize_lets::NormalizeLets::new(false))
-                ))
-                .chain(Optimizer::logical_cleanup_pass(&mut transform_ctx, false).transforms.into_iter())
-                .chain(Optimizer::physical_optimizer(&mut transform_ctx).transforms.into_iter())
-                .collect::<Vec<_>>()
-            };
+    fn full_transform_list() -> Vec<Box<dyn Transform>> {
+        let features = OptimizerFeatures::default();
+        let typecheck_ctx = typecheck::empty_context();
+        let mut df_meta = DataflowMetainfo::default();
+        let mut transform_ctx = TransformCtx::local(
+            &features,
+            &typecheck_ctx,
+            &mut df_meta,
+            None,
+            Some(TEST_GLOBAL_ID),
+        );
+
+        #[allow(deprecated)]
+        Optimizer::logical_optimizer(&mut transform_ctx)
+            .transforms
+            .into_iter()
+            .chain(std::iter::once::<Box<dyn Transform>>(Box::new(
+                mz_transform::movement::ProjectionPushdown::default(),
+            )))
+            .chain(std::iter::once::<Box<dyn Transform>>(Box::new(
+                mz_transform::normalize_lets::NormalizeLets::new(false),
+            )))
+            .chain(Optimizer::logical_cleanup_pass(&mut transform_ctx, false).transforms)
+            .chain(Optimizer::physical_optimizer(&mut transform_ctx).transforms)
+            .collect::<Vec<_>>()
     }
 
     #[derive(Debug, Copy, Clone, PartialEq, Eq)]
@@ -179,7 +185,13 @@ mod tests {
         let features = OptimizerFeatures::default();
         let typecheck_ctx = typecheck::empty_context();
         let mut df_meta = DataflowMetainfo::default();
-        let mut transform_ctx = TransformCtx::local(&features, &typecheck_ctx, &mut df_meta, None);
+        let mut transform_ctx = TransformCtx::local(
+            &features,
+            &typecheck_ctx,
+            &mut df_meta,
+            None,
+            Some(TEST_GLOBAL_ID),
+        );
         let mut rel = parse_relation(s, cat, args)?;
         for t in args.get("apply").cloned().unwrap_or_else(Vec::new).iter() {
             get_transform(t)?.transform(&mut rel, &mut transform_ctx)?;
@@ -188,12 +200,13 @@ mod tests {
         let format_type = get_format_type(args);
 
         let out = match test_type {
-            TestType::Opt => FULL_TRANSFORM_LIST.with(|transforms| -> Result<_, Error> {
+            TestType::Opt => {
+                let transforms = full_transform_list();
                 for transform in transforms.iter() {
                     transform.transform(&mut rel, &mut transform_ctx)?;
                 }
-                Ok(convert_rel_to_string(&rel, cat, &format_type))
-            })?,
+                convert_rel_to_string(&rel, cat, &format_type)
+            }
             TestType::Build => convert_rel_to_string(&rel, cat, &format_type),
             TestType::Steps => {
                 // TODO(justin): this thing does not currently peek into fixpoints, so it's not
@@ -205,32 +218,30 @@ mod tests {
                 writeln!(out, "{}", convert_rel_to_string(&rel, cat, &format_type))?;
                 writeln!(out, "====")?;
 
-                FULL_TRANSFORM_LIST.with(|transforms| -> Result<_, Error> {
-                    for transform in transforms {
-                        let prev = rel.clone();
-                        transform.transform(&mut rel, &mut transform_ctx)?;
-
-                        if rel != prev {
-                            if no_change.len() > 0 {
-                                write!(out, "No change:")?;
-                                let mut sep = " ";
-                                for t in no_change.iter() {
-                                    write!(out, "{}{}", sep, t)?;
-                                    sep = ", ";
-                                }
-                                writeln!(out, "\n====")?;
-                            }
-                            no_change = vec![];
+                let transforms = full_transform_list();
+                for transform in transforms {
+                    let prev = rel.clone();
+                    transform.transform(&mut rel, &mut transform_ctx)?;
 
-                            write!(out, "Applied {:?}:", transform)?;
-                            writeln!(out, "\n{}", convert_rel_to_string(&rel, cat, &format_type))?;
-                            writeln!(out, "====")?;
-                        } else {
-                            no_change.push(format!("{:?}", transform));
+                    if rel != prev {
+                        if no_change.len() > 0 {
+                            write!(out, "No change:")?;
+                            let mut sep = " ";
+                            for t in no_change.iter() {
+                                write!(out, "{}{}", sep, t)?;
+                                sep = ", ";
+                            }
+                            writeln!(out, "\n====")?;
                         }
+                        no_change = vec![];
+
+                        write!(out, "Applied {:?}:", transform)?;
+                        writeln!(out, "\n{}", convert_rel_to_string(&rel, cat, &format_type))?;
+                        writeln!(out, "====")?;
+                    } else {
+                        no_change.push(format!("{:?}", transform));
                     }
-                    Ok(())
-                })?;
+                }
 
                 if no_change.len() > 0 {
                     write!(out, "No change:")?;
@@ -356,14 +367,20 @@ mod tests {
             let features = OptimizerFeatures::default();
             let typecheck_ctx = typecheck::empty_context();
             let mut df_meta = DataflowMetainfo::default();
-            let mut transform_ctx =
-                TransformCtx::local(&features, &typecheck_ctx, &mut df_meta, None);
+            let mut transform_ctx = TransformCtx::local(
+                &features,
+                &typecheck_ctx,
+                &mut df_meta,
+                None,
+                Some(TEST_GLOBAL_ID),
+            );
 
             #[allow(deprecated)]
             let optimizer = Optimizer::logical_optimizer(&mut transform_ctx);
             dataflow = dataflow
                 .into_iter()
                 .map(|(id, rel)| {
+                    transform_ctx.set_global_id(id);
                     (
                         id,
                         optimizer
@@ -391,14 +408,20 @@ mod tests {
             let features = OptimizerFeatures::default();
             let typecheck_ctx = typecheck::empty_context();
             let mut df_meta = DataflowMetainfo::default();
-            let mut transform_ctx =
-                TransformCtx::local(&features, &typecheck_ctx, &mut df_meta, None);
+            let mut transform_ctx = TransformCtx::local(
+                &features,
+                &typecheck_ctx,
+                &mut df_meta,
+                None,
+                Some(TEST_GLOBAL_ID),
+            );
 
             let log_optimizer = Optimizer::logical_cleanup_pass(&mut transform_ctx, true);
             let phys_optimizer = Optimizer::physical_optimizer(&mut transform_ctx);
             dataflow = dataflow
                 .into_iter()
                 .map(|(id, rel)| {
+                    transform_ctx.set_global_id(id);
                     let local_mir_plan = log_optimizer
                         .optimize(rel, &mut transform_ctx)
                         .unwrap()
diff --git a/src/transform/tests/test_transforms.rs b/src/transform/tests/test_transforms.rs
index bc5ce82f96f94..e127f959aade0 100644
--- a/src/transform/tests/test_transforms.rs
+++ b/src/transform/tests/test_transforms.rs
@@ -12,6 +12,7 @@ use std::collections::BTreeSet;
 use mz_expr::explain::{ExplainContext, enforce_linear_chains};
 use mz_expr_parser::{TestCatalog, handle_define, try_parse_mir};
 use mz_ore::str::Indent;
+use mz_repr::GlobalId;
 use mz_repr::explain::text::text_string_at;
 use mz_repr::explain::{ExplainConfig, PlanRenderingContext};
 use mz_repr::optimize::{OptimizerFeatures, OverrideFrom};
@@ -19,6 +20,8 @@ use mz_transform::analysis::annotate_plan;
 use mz_transform::dataflow::DataflowMetainfo;
 use mz_transform::typecheck::TypeErrorHumanizer;
 
+const TEST_GLOBAL_ID: GlobalId = GlobalId::Transient(1234567);
+
 #[mz_ore::test]
 #[cfg_attr(miri, ignore)] // can't call foreign function `rust_psm_stack_pointer` on OS `linux`
 fn run_tests() {
@@ -263,8 +266,13 @@ fn apply_transform<T: mz_transform::Transform>(
     features.enable_dequadratic_eqprop_map = true;
     let typecheck_ctx = mz_transform::typecheck::empty_context();
     let mut df_meta = DataflowMetainfo::default();
-    let mut transform_ctx =
-        mz_transform::TransformCtx::local(&features, &typecheck_ctx, &mut df_meta, None);
+    let mut transform_ctx = mz_transform::TransformCtx::local(
+        &features,
+        &typecheck_ctx,
+        &mut df_meta,
+        None,
+        Some(TEST_GLOBAL_ID),
+    );
 
     // Apply the transformation, returning early on TransformError.
     transform


Reviews:
user: login: ggevay
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T17:21:54Z
user: login: ggevay
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T17:22:28Z
user: login: mgree
body: To confirm my understanding: we weren't setting a `global_id` for these `optimize_mir_local` calls, which meant we weren't ever checking that types were consistent pass to pass.

Looks good to me!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T17:40:01Z
user: login: mgree
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T17:41:31Z
user: login: ggevay
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T12:51:52Z
user: login: ggevay
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T12:52:26Z

Review comments:
diff_hunk: @@ -48,27 +48,39 @@ mod tests {
 
     const TEST_GLOBAL_ID: GlobalId = GlobalId::Transient(1234567);
 
-    thread_local! {
-        static FULL_TRANSFORM_LIST: Vec<Box<dyn Transform>> = {
-            let features = OptimizerFeatures::default();
-            let typecheck_ctx = typecheck::empty_context();
path: src/transform/tests/test_runner.rs
user: login: ggevay
body: The testing bug was that this `typecheck_ctx` used to get shared among different test runs, so Typecheck runs from different test runs stepped on each other's toes through the expected types.
created_at: 2025-05-14T17:21:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -48,27 +48,39 @@ mod tests {
 
     const TEST_GLOBAL_ID: GlobalId = GlobalId::Transient(1234567);
 
-    thread_local! {
-        static FULL_TRANSFORM_LIST: Vec<Box<dyn Transform>> = {
-            let features = OptimizerFeatures::default();
-            let typecheck_ctx = typecheck::empty_context();
path: src/transform/tests/test_runner.rs
user: login: ggevay
body: (This test framework is on its way to be deprecated, so don't think too hard about this code.)
created_at: 2025-05-14T17:22:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -185,7 +186,8 @@ impl<'a> TransformCtx<'a> {
         Arc::clone(self.typecheck_ctx)
     }
 
-    fn set_global_id(&mut self, global_id: GlobalId) {
+    /// Lets self know the id of the object that is being optimized.
path: src/transform/src/lib.rs
user: login: mgree
body: Since `global_id` is `pub`... why not just write the `global_id` in directly?
created_at: 2025-05-14T17:38:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -46,27 +46,41 @@ mod tests {
     const JSON: &str = "json";
     const TEST: &str = "test";
 
-    thread_local! {
-        static FULL_TRANSFORM_LIST: Vec<Box<dyn Transform>> = {
-            let features = OptimizerFeatures::default();
-            let typecheck_ctx = typecheck::empty_context();
-            let mut df_meta = DataflowMetainfo::default();
-            let mut transform_ctx = TransformCtx::local(&features, &typecheck_ctx, &mut df_meta, None);
+    const TEST_GLOBAL_ID: GlobalId = GlobalId::Transient(1234567);
path: src/transform/tests/test_runner.rs
user: login: mgree
body: I don't think we'll ever get up to 12 million built-in things, but would it be possible to dynamically allocate this rather than hardcoding something? (Or adding an `assert!` early on in this testing function that this global ID isn't already in use?)
created_at: 2025-05-14T17:41:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -185,7 +186,8 @@ impl<'a> TransformCtx<'a> {
         Arc::clone(self.typecheck_ctx)
     }
 
-    fn set_global_id(&mut self, global_id: GlobalId) {
+    /// Lets self know the id of the object that is being optimized.
path: src/transform/src/lib.rs
user: login: ggevay
body: When there is a setter, I like to consistently use the setter everywhere. This is because maybe somebody some day will want to add important functionality to the setter.

That said, I don't know why there is a setter in the first place, especially because, as you pointed out, the field itself is pub :sweat_smile: My fingers were also itching to delete the setter, just didn't want to do it in this PR. But open to making a decision on this and opening a separate refactoring PR.
created_at: 2025-05-15T12:51:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -46,27 +46,41 @@ mod tests {
     const JSON: &str = "json";
     const TEST: &str = "test";
 
-    thread_local! {
-        static FULL_TRANSFORM_LIST: Vec<Box<dyn Transform>> = {
-            let features = OptimizerFeatures::default();
-            let typecheck_ctx = typecheck::empty_context();
-            let mut df_meta = DataflowMetainfo::default();
-            let mut transform_ctx = TransformCtx::local(&features, &typecheck_ctx, &mut df_meta, None);
+    const TEST_GLOBAL_ID: GlobalId = GlobalId::Transient(1234567);
path: src/transform/tests/test_runner.rs
user: login: ggevay
body: I think hardcoding this in tests is beneficial, because then you can grep for this specific number when you see it in an error msg.

And I'm not afraid of id clashes, because having a million global objects would put a crazy load on envd, so this is not possible even in a test.
created_at: 2025-05-15T12:52:26Z
author_association: CONTRIBUTOR

PR comments:
user: login: ggevay
created_at: 2025-05-14T13:25:20Z
body: CI is green, so I'm confused. Do we not fail CI even on the more serious kind of Typechecking error? Edit: We do surface these in Sentry, which is great, but CI would also seem important. Or we had hard-to-fix instances is why we disabled it?

Edit: https://github.com/MaterializeInc/materialize/pull/32490 is fixing this.
author_association: CONTRIBUTOR
user: login: ggevay
created_at: 2025-05-14T16:01:44Z
body: > Edit: There are also type errors in src/transform/tests/test_runner.rs! Need to investigate!

I found the the bug: it's in the testing infra, not in the optimizer or in Typecheck. Will fix up in this PR after the meeting.

Edit: Pushed up a commit to fix this.
author_association: CONTRIBUTOR
user: login: ggevay
created_at: 2025-05-15T12:52:55Z
body: > To confirm my understanding: we weren't setting a `global_id` for these `optimize_mir_local` calls, which meant we weren't ever checking that types were consistent pass to pass.

Yes, during local optimization, we were doing only that part of typechecking that is internal to an expression, but we were never comparing the final type of the expression between different parts of the local pipeline.
author_association: CONTRIBUTOR