From bca5f567d25f81e1924b33938a850f89cd8d4770 Mon Sep 17 00:00:00 2001
From: Zalathar <Zalathar@users.noreply.github.com>
Date: Sun, 23 Feb 2025 15:20:53 +1100
Subject: [PATCH 1/4] Add a mir-opt test that demonstrates user type
 annotations

---
 ..._type_annotations.let_else.built.after.mir | 81 +++++++++++++++++++
 ...otations.let_else_bindless.built.after.mir | 63 +++++++++++++++
 ..._type_annotations.let_init.built.after.mir | 54 +++++++++++++
 ...otations.let_init_bindless.built.after.mir | 39 +++++++++
 ...ype_annotations.let_uninit.built.after.mir | 28 +++++++
 ...ations.let_uninit_bindless.built.after.mir | 16 ++++
 ...otations.match_assoc_const.built.after.mir | 46 +++++++++++
 ...ns.match_assoc_const_range.built.after.mir | 74 +++++++++++++++++
 .../mir-opt/building/user_type_annotations.rs | 66 +++++++++++++++
 9 files changed, 467 insertions(+)
 create mode 100644 tests/mir-opt/building/user_type_annotations.let_else.built.after.mir
 create mode 100644 tests/mir-opt/building/user_type_annotations.let_else_bindless.built.after.mir
 create mode 100644 tests/mir-opt/building/user_type_annotations.let_init.built.after.mir
 create mode 100644 tests/mir-opt/building/user_type_annotations.let_init_bindless.built.after.mir
 create mode 100644 tests/mir-opt/building/user_type_annotations.let_uninit.built.after.mir
 create mode 100644 tests/mir-opt/building/user_type_annotations.let_uninit_bindless.built.after.mir
 create mode 100644 tests/mir-opt/building/user_type_annotations.match_assoc_const.built.after.mir
 create mode 100644 tests/mir-opt/building/user_type_annotations.match_assoc_const_range.built.after.mir
 create mode 100644 tests/mir-opt/building/user_type_annotations.rs

diff --git a/tests/mir-opt/building/user_type_annotations.let_else.built.after.mir b/tests/mir-opt/building/user_type_annotations.let_else.built.after.mir
new file mode 100644
index 0000000000000..4029930c855a4
--- /dev/null
+++ b/tests/mir-opt/building/user_type_annotations.let_else.built.after.mir
@@ -0,0 +1,81 @@
+// MIR for `let_else` after built
+
+| User Type Annotations
+| 0: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:35:20: 35:45, inferred_ty: (u32, u64, &char)
+| 1: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:35:20: 35:45, inferred_ty: (u32, u64, &char)
+| 2: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:35:20: 35:45, inferred_ty: (u32, u64, &char)
+|
+fn let_else() -> () {
+    let mut _0: ();
+    let mut _1: !;
+    let _2: u32 as UserTypeProjection { base: UserType(0), projs: [Field(0, ())] };
+    let _3: u64 as UserTypeProjection { base: UserType(0), projs: [Field(1, ())] };
+    let _4: &char as UserTypeProjection { base: UserType(0), projs: [Field(2, ())] };
+    let mut _5: (u32, u64, &char);
+    let mut _6: &char;
+    let _7: &char;
+    let _8: char;
+    scope 1 {
+        debug x => _2;
+        debug y => _3;
+        debug z => _4;
+    }
+
+    bb0: {
+        StorageLive(_2);
+        StorageLive(_3);
+        StorageLive(_4);
+        StorageLive(_5);
+        StorageLive(_6);
+        StorageLive(_7);
+        StorageLive(_8);
+        _8 = const 'u';
+        _7 = &_8;
+        _6 = &(*_7);
+        _5 = (const 7_u32, const 12_u64, move _6);
+        StorageDead(_6);
+        PlaceMention(_5);
+        falseEdge -> [real: bb4, imaginary: bb3];
+    }
+
+    bb1: {
+        _1 = core::panicking::panic(const "internal error: entered unreachable code") -> bb6;
+    }
+
+    bb2: {
+        unreachable;
+    }
+
+    bb3: {
+        goto -> bb5;
+    }
+
+    bb4: {
+        AscribeUserType(_5, +, UserTypeProjection { base: UserType(2), projs: [] });
+        _2 = copy (_5.0: u32);
+        _3 = copy (_5.1: u64);
+        _4 = copy (_5.2: &char);
+        StorageDead(_7);
+        StorageDead(_5);
+        _0 = const ();
+        StorageDead(_8);
+        StorageDead(_4);
+        StorageDead(_3);
+        StorageDead(_2);
+        return;
+    }
+
+    bb5: {
+        StorageDead(_7);
+        StorageDead(_5);
+        StorageDead(_8);
+        StorageDead(_4);
+        StorageDead(_3);
+        StorageDead(_2);
+        goto -> bb1;
+    }
+
+    bb6 (cleanup): {
+        resume;
+    }
+}
diff --git a/tests/mir-opt/building/user_type_annotations.let_else_bindless.built.after.mir b/tests/mir-opt/building/user_type_annotations.let_else_bindless.built.after.mir
new file mode 100644
index 0000000000000..ffac242e54ed2
--- /dev/null
+++ b/tests/mir-opt/building/user_type_annotations.let_else_bindless.built.after.mir
@@ -0,0 +1,63 @@
+// MIR for `let_else_bindless` after built
+
+| User Type Annotations
+| 0: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:40:20: 40:45, inferred_ty: (u32, u64, &char)
+| 1: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:40:20: 40:45, inferred_ty: (u32, u64, &char)
+| 2: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:40:20: 40:45, inferred_ty: (u32, u64, &char)
+|
+fn let_else_bindless() -> () {
+    let mut _0: ();
+    let mut _1: !;
+    let mut _2: (u32, u64, &char);
+    let mut _3: &char;
+    let _4: &char;
+    let _5: char;
+    scope 1 {
+    }
+
+    bb0: {
+        StorageLive(_2);
+        StorageLive(_3);
+        StorageLive(_4);
+        StorageLive(_5);
+        _5 = const 'u';
+        _4 = &_5;
+        _3 = &(*_4);
+        _2 = (const 7_u32, const 12_u64, move _3);
+        StorageDead(_3);
+        PlaceMention(_2);
+        falseEdge -> [real: bb4, imaginary: bb3];
+    }
+
+    bb1: {
+        _1 = core::panicking::panic(const "internal error: entered unreachable code") -> bb6;
+    }
+
+    bb2: {
+        unreachable;
+    }
+
+    bb3: {
+        goto -> bb5;
+    }
+
+    bb4: {
+        AscribeUserType(_2, +, UserTypeProjection { base: UserType(2), projs: [] });
+        StorageDead(_4);
+        StorageDead(_2);
+        _0 = const ();
+        StorageDead(_5);
+        return;
+    }
+
+    bb5: {
+        StorageDead(_4);
+        StorageDead(_2);
+        StorageDead(_5);
+        goto -> bb1;
+    }
+
+    bb6 (cleanup): {
+        resume;
+    }
+}
diff --git a/tests/mir-opt/building/user_type_annotations.let_init.built.after.mir b/tests/mir-opt/building/user_type_annotations.let_init.built.after.mir
new file mode 100644
index 0000000000000..d1b8f823e9bc5
--- /dev/null
+++ b/tests/mir-opt/building/user_type_annotations.let_init.built.after.mir
@@ -0,0 +1,54 @@
+// MIR for `let_init` after built
+
+| User Type Annotations
+| 0: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:25:20: 25:45, inferred_ty: (u32, u64, &char)
+| 1: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:25:20: 25:45, inferred_ty: (u32, u64, &char)
+|
+fn let_init() -> () {
+    let mut _0: ();
+    let _1: u32 as UserTypeProjection { base: UserType(0), projs: [Field(0, ())] };
+    let _2: u64 as UserTypeProjection { base: UserType(0), projs: [Field(1, ())] };
+    let _3: &char as UserTypeProjection { base: UserType(0), projs: [Field(2, ())] };
+    let mut _4: (u32, u64, &char);
+    let mut _5: &char;
+    let _6: &char;
+    let _7: char;
+    scope 1 {
+        debug x => _1;
+        debug y => _2;
+        debug z => _3;
+    }
+
+    bb0: {
+        StorageLive(_4);
+        StorageLive(_5);
+        StorageLive(_6);
+        StorageLive(_7);
+        _7 = const 'u';
+        _6 = &_7;
+        _5 = &(*_6);
+        _4 = (const 7_u32, const 12_u64, move _5);
+        StorageDead(_5);
+        PlaceMention(_4);
+        AscribeUserType(_4, +, UserTypeProjection { base: UserType(1), projs: [] });
+        StorageLive(_1);
+        _1 = copy (_4.0: u32);
+        StorageLive(_2);
+        _2 = copy (_4.1: u64);
+        StorageLive(_3);
+        _3 = copy (_4.2: &char);
+        StorageDead(_6);
+        StorageDead(_4);
+        _0 = const ();
+        StorageDead(_3);
+        StorageDead(_2);
+        StorageDead(_1);
+        StorageDead(_7);
+        return;
+    }
+
+    bb1: {
+        FakeRead(ForMatchedPlace(None), _4);
+        unreachable;
+    }
+}
diff --git a/tests/mir-opt/building/user_type_annotations.let_init_bindless.built.after.mir b/tests/mir-opt/building/user_type_annotations.let_init_bindless.built.after.mir
new file mode 100644
index 0000000000000..6702f9300607f
--- /dev/null
+++ b/tests/mir-opt/building/user_type_annotations.let_init_bindless.built.after.mir
@@ -0,0 +1,39 @@
+// MIR for `let_init_bindless` after built
+
+| User Type Annotations
+| 0: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:30:20: 30:45, inferred_ty: (u32, u64, &char)
+| 1: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:30:20: 30:45, inferred_ty: (u32, u64, &char)
+|
+fn let_init_bindless() -> () {
+    let mut _0: ();
+    let mut _1: (u32, u64, &char);
+    let mut _2: &char;
+    let _3: &char;
+    let _4: char;
+    scope 1 {
+    }
+
+    bb0: {
+        StorageLive(_1);
+        StorageLive(_2);
+        StorageLive(_3);
+        StorageLive(_4);
+        _4 = const 'u';
+        _3 = &_4;
+        _2 = &(*_3);
+        _1 = (const 7_u32, const 12_u64, move _2);
+        StorageDead(_2);
+        PlaceMention(_1);
+        AscribeUserType(_1, +, UserTypeProjection { base: UserType(1), projs: [] });
+        StorageDead(_3);
+        StorageDead(_1);
+        _0 = const ();
+        StorageDead(_4);
+        return;
+    }
+
+    bb1: {
+        FakeRead(ForMatchedPlace(None), _1);
+        unreachable;
+    }
+}
diff --git a/tests/mir-opt/building/user_type_annotations.let_uninit.built.after.mir b/tests/mir-opt/building/user_type_annotations.let_uninit.built.after.mir
new file mode 100644
index 0000000000000..d86cbc5b192c1
--- /dev/null
+++ b/tests/mir-opt/building/user_type_annotations.let_uninit.built.after.mir
@@ -0,0 +1,28 @@
+// MIR for `let_uninit` after built
+
+| User Type Annotations
+| 0: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:15:20: 15:45, inferred_ty: (u32, u64, &char)
+| 1: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:15:20: 15:45, inferred_ty: (u32, u64, &char)
+|
+fn let_uninit() -> () {
+    let mut _0: ();
+    let _1: u32 as UserTypeProjection { base: UserType(0), projs: [Field(0, ())] };
+    let _2: u64 as UserTypeProjection { base: UserType(0), projs: [Field(1, ())] };
+    let _3: &char as UserTypeProjection { base: UserType(0), projs: [Field(2, ())] };
+    scope 1 {
+        debug x => _1;
+        debug y => _2;
+        debug z => _3;
+    }
+
+    bb0: {
+        StorageLive(_1);
+        StorageLive(_2);
+        StorageLive(_3);
+        _0 = const ();
+        StorageDead(_3);
+        StorageDead(_2);
+        StorageDead(_1);
+        return;
+    }
+}
diff --git a/tests/mir-opt/building/user_type_annotations.let_uninit_bindless.built.after.mir b/tests/mir-opt/building/user_type_annotations.let_uninit_bindless.built.after.mir
new file mode 100644
index 0000000000000..a85bdacad235e
--- /dev/null
+++ b/tests/mir-opt/building/user_type_annotations.let_uninit_bindless.built.after.mir
@@ -0,0 +1,16 @@
+// MIR for `let_uninit_bindless` after built
+
+| User Type Annotations
+| 0: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:20:20: 20:45, inferred_ty: (u32, u64, &char)
+| 1: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:20:20: 20:45, inferred_ty: (u32, u64, &char)
+|
+fn let_uninit_bindless() -> () {
+    let mut _0: ();
+    scope 1 {
+    }
+
+    bb0: {
+        _0 = const ();
+        return;
+    }
+}
diff --git a/tests/mir-opt/building/user_type_annotations.match_assoc_const.built.after.mir b/tests/mir-opt/building/user_type_annotations.match_assoc_const.built.after.mir
new file mode 100644
index 0000000000000..c0ce6f1d06b5e
--- /dev/null
+++ b/tests/mir-opt/building/user_type_annotations.match_assoc_const.built.after.mir
@@ -0,0 +1,46 @@
+// MIR for `match_assoc_const` after built
+
+| User Type Annotations
+| 0: user_ty: Canonical { value: TypeOf(DefId(0:11 ~ user_type_annotations[ee8e]::MyTrait::FOO), UserArgs { args: [MyStruct, 'static], user_self_ty: None }), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:54:9: 54:44, inferred_ty: u32
+| 1: user_ty: Canonical { value: TypeOf(DefId(0:11 ~ user_type_annotations[ee8e]::MyTrait::FOO), UserArgs { args: [MyStruct, 'static], user_self_ty: None }), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:54:9: 54:44, inferred_ty: u32
+|
+fn match_assoc_const() -> () {
+    let mut _0: ();
+    let mut _1: u32;
+
+    bb0: {
+        StorageLive(_1);
+        _1 = const 8_u32;
+        PlaceMention(_1);
+        switchInt(copy _1) -> [99: bb2, otherwise: bb1];
+    }
+
+    bb1: {
+        _0 = const ();
+        goto -> bb6;
+    }
+
+    bb2: {
+        falseEdge -> [real: bb5, imaginary: bb1];
+    }
+
+    bb3: {
+        goto -> bb1;
+    }
+
+    bb4: {
+        FakeRead(ForMatchedPlace(None), _1);
+        unreachable;
+    }
+
+    bb5: {
+        AscribeUserType(_1, -, UserTypeProjection { base: UserType(1), projs: [] });
+        _0 = const ();
+        goto -> bb6;
+    }
+
+    bb6: {
+        StorageDead(_1);
+        return;
+    }
+}
diff --git a/tests/mir-opt/building/user_type_annotations.match_assoc_const_range.built.after.mir b/tests/mir-opt/building/user_type_annotations.match_assoc_const_range.built.after.mir
new file mode 100644
index 0000000000000..3a6aa5b7c2ca4
--- /dev/null
+++ b/tests/mir-opt/building/user_type_annotations.match_assoc_const_range.built.after.mir
@@ -0,0 +1,74 @@
+// MIR for `match_assoc_const_range` after built
+
+| User Type Annotations
+| 0: user_ty: Canonical { value: TypeOf(DefId(0:11 ~ user_type_annotations[ee8e]::MyTrait::FOO), UserArgs { args: [MyStruct, 'static], user_self_ty: None }), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:62:11: 62:46, inferred_ty: u32
+| 1: user_ty: Canonical { value: TypeOf(DefId(0:11 ~ user_type_annotations[ee8e]::MyTrait::FOO), UserArgs { args: [MyStruct, 'static], user_self_ty: None }), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:62:11: 62:46, inferred_ty: u32
+| 2: user_ty: Canonical { value: TypeOf(DefId(0:11 ~ user_type_annotations[ee8e]::MyTrait::FOO), UserArgs { args: [MyStruct, 'static], user_self_ty: None }), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:63:9: 63:44, inferred_ty: u32
+| 3: user_ty: Canonical { value: TypeOf(DefId(0:11 ~ user_type_annotations[ee8e]::MyTrait::FOO), UserArgs { args: [MyStruct, 'static], user_self_ty: None }), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:63:9: 63:44, inferred_ty: u32
+|
+fn match_assoc_const_range() -> () {
+    let mut _0: ();
+    let mut _1: u32;
+    let mut _2: bool;
+    let mut _3: bool;
+
+    bb0: {
+        StorageLive(_1);
+        _1 = const 8_u32;
+        PlaceMention(_1);
+        _3 = Lt(copy _1, const 99_u32);
+        switchInt(move _3) -> [0: bb4, otherwise: bb2];
+    }
+
+    bb1: {
+        _0 = const ();
+        goto -> bb11;
+    }
+
+    bb2: {
+        falseEdge -> [real: bb10, imaginary: bb4];
+    }
+
+    bb3: {
+        goto -> bb1;
+    }
+
+    bb4: {
+        _2 = Le(const 99_u32, copy _1);
+        switchInt(move _2) -> [0: bb5, otherwise: bb6];
+    }
+
+    bb5: {
+        goto -> bb1;
+    }
+
+    bb6: {
+        falseEdge -> [real: bb9, imaginary: bb1];
+    }
+
+    bb7: {
+        goto -> bb5;
+    }
+
+    bb8: {
+        FakeRead(ForMatchedPlace(None), _1);
+        unreachable;
+    }
+
+    bb9: {
+        AscribeUserType(_1, -, UserTypeProjection { base: UserType(3), projs: [] });
+        _0 = const ();
+        goto -> bb11;
+    }
+
+    bb10: {
+        AscribeUserType(_1, -, UserTypeProjection { base: UserType(1), projs: [] });
+        _0 = const ();
+        goto -> bb11;
+    }
+
+    bb11: {
+        StorageDead(_1);
+        return;
+    }
+}
diff --git a/tests/mir-opt/building/user_type_annotations.rs b/tests/mir-opt/building/user_type_annotations.rs
new file mode 100644
index 0000000000000..d55c678d5ae35
--- /dev/null
+++ b/tests/mir-opt/building/user_type_annotations.rs
@@ -0,0 +1,66 @@
+//@ edition: 2024
+// skip-filecheck
+
+// This test demonstrates how many user type annotations are recorded in MIR
+// for various binding constructs. In particular, this makes it possible to see
+// the number of duplicate user-type-annotation entries, and whether that
+// number has changed.
+//
+// Duplicates are mostly harmless, other than being inefficient.
+// "Unused" entries that are _not_ duplicates may nevertheless be necessary so
+// that they are seen by MIR lifetime checks.
+
+// EMIT_MIR user_type_annotations.let_uninit.built.after.mir
+fn let_uninit() {
+    let (x, y, z): (u32, u64, &'static char);
+}
+
+// EMIT_MIR user_type_annotations.let_uninit_bindless.built.after.mir
+fn let_uninit_bindless() {
+    let (_, _, _): (u32, u64, &'static char);
+}
+
+// EMIT_MIR user_type_annotations.let_init.built.after.mir
+fn let_init() {
+    let (x, y, z): (u32, u64, &'static char) = (7, 12, &'u');
+}
+
+// EMIT_MIR user_type_annotations.let_init_bindless.built.after.mir
+fn let_init_bindless() {
+    let (_, _, _): (u32, u64, &'static char) = (7, 12, &'u');
+}
+
+// EMIT_MIR user_type_annotations.let_else.built.after.mir
+fn let_else() {
+    let (x, y, z): (u32, u64, &'static char) = (7, 12, &'u') else { unreachable!() };
+}
+
+// EMIT_MIR user_type_annotations.let_else_bindless.built.after.mir
+fn let_else_bindless() {
+    let (_, _, _): (u32, u64, &'static char) = (7, 12, &'u') else { unreachable!() };
+}
+
+trait MyTrait<'a> {
+    const FOO: u32;
+}
+struct MyStruct {}
+impl MyTrait<'static> for MyStruct {
+    const FOO: u32 = 99;
+}
+
+// EMIT_MIR user_type_annotations.match_assoc_const.built.after.mir
+fn match_assoc_const() {
+    match 8 {
+        <MyStruct as MyTrait<'static>>::FOO => {}
+        _ => {}
+    }
+}
+
+// EMIT_MIR user_type_annotations.match_assoc_const_range.built.after.mir
+fn match_assoc_const_range() {
+    match 8 {
+        ..<MyStruct as MyTrait<'static>>::FOO => {}
+        <MyStruct as MyTrait<'static>>::FOO.. => {}
+        _ => {}
+    }
+}

From 977106a215f53cf115cc37cca22cea48501b41e0 Mon Sep 17 00:00:00 2001
From: Zalathar <Zalathar@users.noreply.github.com>
Date: Sun, 16 Feb 2025 17:43:11 +1100
Subject: [PATCH 2/4] Simplify handling of `visibility_scope` in
 `declare_bindings`

This avoids the need to unwrap an option after ensuring that it is some.
---
 compiler/rustc_mir_build/src/builder/matches/mod.rs | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs
index b05052a3455ea..89faf705d19ac 100644
--- a/compiler/rustc_mir_build/src/builder/matches/mod.rs
+++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs
@@ -759,15 +759,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
             pattern,
             UserTypeProjections::none(),
             &mut |this, name, mode, var, span, ty, user_ty| {
-                if visibility_scope.is_none() {
-                    visibility_scope =
-                        Some(this.new_source_scope(scope_span, LintLevel::Inherited));
-                }
+                let vis_scope = *visibility_scope
+                    .get_or_insert_with(|| this.new_source_scope(scope_span, LintLevel::Inherited));
                 let source_info = SourceInfo { span, scope: this.source_scope };
-                let visibility_scope = visibility_scope.unwrap();
+
                 this.declare_binding(
                     source_info,
-                    visibility_scope,
+                    vis_scope,
                     name,
                     mode,
                     var,

From 7805b465fdb91664a3a41192f4ce40aff313131f Mon Sep 17 00:00:00 2001
From: Zalathar <Zalathar@users.noreply.github.com>
Date: Sun, 16 Feb 2025 00:57:37 +1100
Subject: [PATCH 3/4] Split `visit_primary_bindings` into two variants

The existing method does some non-obvious extra work to collect user types and
build user-type projections, which is specifically needed by `declare_bindings`
and not by the other two callers.
---
 compiler/rustc_middle/src/thir.rs             |  4 ++
 compiler/rustc_mir_build/src/builder/block.rs | 46 ++++++-------
 .../src/builder/matches/mod.rs                | 65 ++++++++++++-------
 ..._type_annotations.let_else.built.after.mir |  3 +-
 ...otations.let_else_bindless.built.after.mir |  3 +-
 ...ype_annotations.let_uninit.built.after.mir |  1 -
 ...ations.let_uninit_bindless.built.after.mir |  1 -
 7 files changed, 67 insertions(+), 56 deletions(-)

diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs
index f7b98d935d4de..1056644b813a2 100644
--- a/compiler/rustc_middle/src/thir.rs
+++ b/compiler/rustc_middle/src/thir.rs
@@ -783,8 +783,12 @@ pub enum PatKind<'tcx> {
         var: LocalVarId,
         ty: Ty<'tcx>,
         subpattern: Option<Box<Pat<'tcx>>>,
+
         /// Is this the leftmost occurrence of the binding, i.e., is `var` the
         /// `HirId` of this pattern?
+        ///
+        /// (The same binding can occur multiple times in different branches of
+        /// an or-pattern, but only one of them will be primary.)
         is_primary: bool,
     },
 
diff --git a/compiler/rustc_mir_build/src/builder/block.rs b/compiler/rustc_mir_build/src/builder/block.rs
index 7c76e02fcef6a..a71196f79d78d 100644
--- a/compiler/rustc_mir_build/src/builder/block.rs
+++ b/compiler/rustc_mir_build/src/builder/block.rs
@@ -199,19 +199,15 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                             None,
                             Some((Some(&destination), initializer_span)),
                         );
-                        this.visit_primary_bindings(
-                            pattern,
-                            UserTypeProjections::none(),
-                            &mut |this, _, _, node, span, _, _| {
-                                this.storage_live_binding(
-                                    block,
-                                    node,
-                                    span,
-                                    OutsideGuard,
-                                    ScheduleDrops::Yes,
-                                );
-                            },
-                        );
+                        this.visit_primary_bindings(pattern, &mut |this, node, span| {
+                            this.storage_live_binding(
+                                block,
+                                node,
+                                span,
+                                OutsideGuard,
+                                ScheduleDrops::Yes,
+                            );
+                        });
                         let else_block_span = this.thir[*else_block].span;
                         let (matching, failure) =
                             this.in_if_then_scope(last_remainder_scope, else_block_span, |this| {
@@ -295,20 +291,16 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                         });
 
                         debug!("ast_block_stmts: pattern={:?}", pattern);
-                        this.visit_primary_bindings(
-                            pattern,
-                            UserTypeProjections::none(),
-                            &mut |this, _, _, node, span, _, _| {
-                                this.storage_live_binding(
-                                    block,
-                                    node,
-                                    span,
-                                    OutsideGuard,
-                                    ScheduleDrops::Yes,
-                                );
-                                this.schedule_drop_for_binding(node, span, OutsideGuard);
-                            },
-                        )
+                        this.visit_primary_bindings(pattern, &mut |this, node, span| {
+                            this.storage_live_binding(
+                                block,
+                                node,
+                                span,
+                                OutsideGuard,
+                                ScheduleDrops::Yes,
+                            );
+                            this.schedule_drop_for_binding(node, span, OutsideGuard);
+                        })
                     }
 
                     // Enter the visibility scope, after evaluating the initializer.
diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs
index 89faf705d19ac..60cd8a2c89cb7 100644
--- a/compiler/rustc_mir_build/src/builder/matches/mod.rs
+++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs
@@ -755,7 +755,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         guard: Option<ExprId>,
         opt_match_place: Option<(Option<&Place<'tcx>>, Span)>,
     ) -> Option<SourceScope> {
-        self.visit_primary_bindings(
+        self.visit_primary_bindings_special(
             pattern,
             UserTypeProjections::none(),
             &mut |this, name, mode, var, span, ty, user_ty| {
@@ -846,10 +846,32 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         }
     }
 
-    /// Visit all of the primary bindings in a patterns, that is, visit the
-    /// leftmost occurrence of each variable bound in a pattern. A variable
-    /// will occur more than once in an or-pattern.
+    /// Visits all of the "primary" bindings in a pattern, i.e. the leftmost
+    /// occurrence of each variable bound by the pattern.
+    /// See [`PatKind::Binding::is_primary`] for more context.
+    ///
+    /// This variant provides only the limited subset of binding data needed
+    /// by its callers, and should be a "pure" visit without side-effects.
     pub(super) fn visit_primary_bindings(
+        &mut self,
+        pattern: &Pat<'tcx>,
+        f: &mut impl FnMut(&mut Self, LocalVarId, Span),
+    ) {
+        pattern.walk_always(|pat| {
+            if let PatKind::Binding { var, is_primary: true, .. } = pat.kind {
+                f(self, var, pat.span);
+            }
+        })
+    }
+
+    /// Visits all of the "primary" bindings in a pattern, while preparing
+    /// additional user-type-annotation data needed by `declare_bindings`.
+    ///
+    /// This also has the side-effect of pushing all user type annotations
+    /// onto `canonical_user_type_annotations`, so that they end up in MIR
+    /// even if they aren't associated with any bindings.
+    #[instrument(level = "debug", skip(self, f))]
+    fn visit_primary_bindings_special(
         &mut self,
         pattern: &Pat<'tcx>,
         pattern_user_ty: UserTypeProjections,
@@ -863,17 +885,18 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
             UserTypeProjections,
         ),
     ) {
-        debug!(
-            "visit_primary_bindings: pattern={:?} pattern_user_ty={:?}",
-            pattern, pattern_user_ty
-        );
+        // Avoid having to write the full method name at each recursive call.
+        let visit_subpat = |this: &mut Self, subpat, user_tys, f: &mut _| {
+            this.visit_primary_bindings_special(subpat, user_tys, f)
+        };
+
         match pattern.kind {
             PatKind::Binding { name, mode, var, ty, ref subpattern, is_primary, .. } => {
                 if is_primary {
                     f(self, name, mode, var, pattern.span, ty, pattern_user_ty.clone());
                 }
                 if let Some(subpattern) = subpattern.as_ref() {
-                    self.visit_primary_bindings(subpattern, pattern_user_ty, f);
+                    visit_subpat(self, subpattern, pattern_user_ty, f);
                 }
             }
 
@@ -882,17 +905,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 let from = u64::try_from(prefix.len()).unwrap();
                 let to = u64::try_from(suffix.len()).unwrap();
                 for subpattern in prefix.iter() {
-                    self.visit_primary_bindings(subpattern, pattern_user_ty.clone().index(), f);
+                    visit_subpat(self, subpattern, pattern_user_ty.clone().index(), f);
                 }
                 if let Some(subpattern) = slice {
-                    self.visit_primary_bindings(
-                        subpattern,
-                        pattern_user_ty.clone().subslice(from, to),
-                        f,
-                    );
+                    visit_subpat(self, subpattern, pattern_user_ty.clone().subslice(from, to), f);
                 }
                 for subpattern in suffix.iter() {
-                    self.visit_primary_bindings(subpattern, pattern_user_ty.clone().index(), f);
+                    visit_subpat(self, subpattern, pattern_user_ty.clone().index(), f);
                 }
             }
 
@@ -903,11 +922,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
             | PatKind::Error(_) => {}
 
             PatKind::Deref { ref subpattern } => {
-                self.visit_primary_bindings(subpattern, pattern_user_ty.deref(), f);
+                visit_subpat(self, subpattern, pattern_user_ty.deref(), f);
             }
 
             PatKind::DerefPattern { ref subpattern, .. } => {
-                self.visit_primary_bindings(subpattern, UserTypeProjections::none(), f);
+                visit_subpat(self, subpattern, UserTypeProjections::none(), f);
             }
 
             PatKind::AscribeUserType {
@@ -925,18 +944,18 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
 
                 let base_user_ty = self.canonical_user_type_annotations.push(annotation.clone());
                 let subpattern_user_ty = pattern_user_ty.push_user_type(base_user_ty);
-                self.visit_primary_bindings(subpattern, subpattern_user_ty, f)
+                visit_subpat(self, subpattern, subpattern_user_ty, f)
             }
 
             PatKind::ExpandedConstant { ref subpattern, .. } => {
-                self.visit_primary_bindings(subpattern, pattern_user_ty, f)
+                visit_subpat(self, subpattern, pattern_user_ty, f)
             }
 
             PatKind::Leaf { ref subpatterns } => {
                 for subpattern in subpatterns {
                     let subpattern_user_ty = pattern_user_ty.clone().leaf(subpattern.field);
                     debug!("visit_primary_bindings: subpattern_user_ty={:?}", subpattern_user_ty);
-                    self.visit_primary_bindings(&subpattern.pattern, subpattern_user_ty, f);
+                    visit_subpat(self, &subpattern.pattern, subpattern_user_ty, f);
                 }
             }
 
@@ -944,7 +963,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 for subpattern in subpatterns {
                     let subpattern_user_ty =
                         pattern_user_ty.clone().variant(adt_def, variant_index, subpattern.field);
-                    self.visit_primary_bindings(&subpattern.pattern, subpattern_user_ty, f);
+                    visit_subpat(self, &subpattern.pattern, subpattern_user_ty, f);
                 }
             }
             PatKind::Or { ref pats } => {
@@ -953,7 +972,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 // `let (x | y) = ...`, the primary binding of `y` occurs in
                 // the right subpattern
                 for subpattern in pats.iter() {
-                    self.visit_primary_bindings(subpattern, pattern_user_ty.clone(), f);
+                    visit_subpat(self, subpattern, pattern_user_ty.clone(), f);
                 }
             }
         }
diff --git a/tests/mir-opt/building/user_type_annotations.let_else.built.after.mir b/tests/mir-opt/building/user_type_annotations.let_else.built.after.mir
index 4029930c855a4..3a515787c10b0 100644
--- a/tests/mir-opt/building/user_type_annotations.let_else.built.after.mir
+++ b/tests/mir-opt/building/user_type_annotations.let_else.built.after.mir
@@ -3,7 +3,6 @@
 | User Type Annotations
 | 0: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:35:20: 35:45, inferred_ty: (u32, u64, &char)
 | 1: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:35:20: 35:45, inferred_ty: (u32, u64, &char)
-| 2: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:35:20: 35:45, inferred_ty: (u32, u64, &char)
 |
 fn let_else() -> () {
     let mut _0: ();
@@ -51,7 +50,7 @@ fn let_else() -> () {
     }
 
     bb4: {
-        AscribeUserType(_5, +, UserTypeProjection { base: UserType(2), projs: [] });
+        AscribeUserType(_5, +, UserTypeProjection { base: UserType(1), projs: [] });
         _2 = copy (_5.0: u32);
         _3 = copy (_5.1: u64);
         _4 = copy (_5.2: &char);
diff --git a/tests/mir-opt/building/user_type_annotations.let_else_bindless.built.after.mir b/tests/mir-opt/building/user_type_annotations.let_else_bindless.built.after.mir
index ffac242e54ed2..52a6d904d4580 100644
--- a/tests/mir-opt/building/user_type_annotations.let_else_bindless.built.after.mir
+++ b/tests/mir-opt/building/user_type_annotations.let_else_bindless.built.after.mir
@@ -3,7 +3,6 @@
 | User Type Annotations
 | 0: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:40:20: 40:45, inferred_ty: (u32, u64, &char)
 | 1: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:40:20: 40:45, inferred_ty: (u32, u64, &char)
-| 2: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:40:20: 40:45, inferred_ty: (u32, u64, &char)
 |
 fn let_else_bindless() -> () {
     let mut _0: ();
@@ -42,7 +41,7 @@ fn let_else_bindless() -> () {
     }
 
     bb4: {
-        AscribeUserType(_2, +, UserTypeProjection { base: UserType(2), projs: [] });
+        AscribeUserType(_2, +, UserTypeProjection { base: UserType(1), projs: [] });
         StorageDead(_4);
         StorageDead(_2);
         _0 = const ();
diff --git a/tests/mir-opt/building/user_type_annotations.let_uninit.built.after.mir b/tests/mir-opt/building/user_type_annotations.let_uninit.built.after.mir
index d86cbc5b192c1..76b5938b87d2a 100644
--- a/tests/mir-opt/building/user_type_annotations.let_uninit.built.after.mir
+++ b/tests/mir-opt/building/user_type_annotations.let_uninit.built.after.mir
@@ -2,7 +2,6 @@
 
 | User Type Annotations
 | 0: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:15:20: 15:45, inferred_ty: (u32, u64, &char)
-| 1: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:15:20: 15:45, inferred_ty: (u32, u64, &char)
 |
 fn let_uninit() -> () {
     let mut _0: ();
diff --git a/tests/mir-opt/building/user_type_annotations.let_uninit_bindless.built.after.mir b/tests/mir-opt/building/user_type_annotations.let_uninit_bindless.built.after.mir
index a85bdacad235e..0cd125587714d 100644
--- a/tests/mir-opt/building/user_type_annotations.let_uninit_bindless.built.after.mir
+++ b/tests/mir-opt/building/user_type_annotations.let_uninit_bindless.built.after.mir
@@ -2,7 +2,6 @@
 
 | User Type Annotations
 | 0: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:20:20: 20:45, inferred_ty: (u32, u64, &char)
-| 1: user_ty: Canonical { value: Ty((u32, u64, &'static char)), max_universe: U0, variables: [] }, span: $DIR/user_type_annotations.rs:20:20: 20:45, inferred_ty: (u32, u64, &char)
 |
 fn let_uninit_bindless() -> () {
     let mut _0: ();

From 5434242af764d1525bd6ddf6e53ee5567042e381 Mon Sep 17 00:00:00 2001
From: Zalathar <Zalathar@users.noreply.github.com>
Date: Thu, 20 Feb 2025 21:44:01 +1100
Subject: [PATCH 4/4] Build `UserTypeProjections` lazily when visiting bindings

---
 compiler/rustc_middle/src/mir/mod.rs          |  85 +----------
 .../src/builder/matches/mod.rs                |  66 +++++----
 .../src/builder/matches/user_ty.rs            | 140 ++++++++++++++++++
 3 files changed, 179 insertions(+), 112 deletions(-)
 create mode 100644 compiler/rustc_mir_build/src/builder/matches/user_ty.rs

diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs
index 7090e93549e69..4dfb362f3a22b 100644
--- a/compiler/rustc_middle/src/mir/mod.rs
+++ b/compiler/rustc_middle/src/mir/mod.rs
@@ -33,8 +33,8 @@ use crate::mir::interpret::{AllocRange, Scalar};
 use crate::ty::codec::{TyDecoder, TyEncoder};
 use crate::ty::print::{FmtPrinter, Printer, pretty_print_const, with_no_trimmed_paths};
 use crate::ty::{
-    self, AdtDef, GenericArg, GenericArgsRef, Instance, InstanceKind, List, Ty, TyCtxt,
-    TypeVisitableExt, TypingEnv, UserTypeAnnotationIndex,
+    self, GenericArg, GenericArgsRef, Instance, InstanceKind, List, Ty, TyCtxt, TypeVisitableExt,
+    TypingEnv, UserTypeAnnotationIndex,
 };
 
 mod basic_blocks;
@@ -1482,53 +1482,10 @@ pub struct UserTypeProjections {
     pub contents: Vec<UserTypeProjection>,
 }
 
-impl<'tcx> UserTypeProjections {
-    pub fn none() -> Self {
-        UserTypeProjections { contents: vec![] }
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.contents.is_empty()
-    }
-
+impl UserTypeProjections {
     pub fn projections(&self) -> impl Iterator<Item = &UserTypeProjection> + ExactSizeIterator {
         self.contents.iter()
     }
-
-    pub fn push_user_type(mut self, base_user_type: UserTypeAnnotationIndex) -> Self {
-        self.contents.push(UserTypeProjection { base: base_user_type, projs: vec![] });
-        self
-    }
-
-    fn map_projections(mut self, f: impl FnMut(UserTypeProjection) -> UserTypeProjection) -> Self {
-        self.contents = self.contents.into_iter().map(f).collect();
-        self
-    }
-
-    pub fn index(self) -> Self {
-        self.map_projections(|pat_ty_proj| pat_ty_proj.index())
-    }
-
-    pub fn subslice(self, from: u64, to: u64) -> Self {
-        self.map_projections(|pat_ty_proj| pat_ty_proj.subslice(from, to))
-    }
-
-    pub fn deref(self) -> Self {
-        self.map_projections(|pat_ty_proj| pat_ty_proj.deref())
-    }
-
-    pub fn leaf(self, field: FieldIdx) -> Self {
-        self.map_projections(|pat_ty_proj| pat_ty_proj.leaf(field))
-    }
-
-    pub fn variant(
-        self,
-        adt_def: AdtDef<'tcx>,
-        variant_index: VariantIdx,
-        field_index: FieldIdx,
-    ) -> Self {
-        self.map_projections(|pat_ty_proj| pat_ty_proj.variant(adt_def, variant_index, field_index))
-    }
 }
 
 /// Encodes the effect of a user-supplied type annotation on the
@@ -1553,42 +1510,6 @@ pub struct UserTypeProjection {
     pub projs: Vec<ProjectionKind>,
 }
 
-impl UserTypeProjection {
-    pub(crate) fn index(mut self) -> Self {
-        self.projs.push(ProjectionElem::Index(()));
-        self
-    }
-
-    pub(crate) fn subslice(mut self, from: u64, to: u64) -> Self {
-        self.projs.push(ProjectionElem::Subslice { from, to, from_end: true });
-        self
-    }
-
-    pub(crate) fn deref(mut self) -> Self {
-        self.projs.push(ProjectionElem::Deref);
-        self
-    }
-
-    pub(crate) fn leaf(mut self, field: FieldIdx) -> Self {
-        self.projs.push(ProjectionElem::Field(field, ()));
-        self
-    }
-
-    pub(crate) fn variant(
-        mut self,
-        adt_def: AdtDef<'_>,
-        variant_index: VariantIdx,
-        field_index: FieldIdx,
-    ) -> Self {
-        self.projs.push(ProjectionElem::Downcast(
-            Some(adt_def.variant(variant_index).name),
-            variant_index,
-        ));
-        self.projs.push(ProjectionElem::Field(field_index, ()));
-        self
-    }
-}
-
 rustc_index::newtype_index! {
     #[derive(HashStable)]
     #[encodable]
diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs
index 60cd8a2c89cb7..ea341b604e0be 100644
--- a/compiler/rustc_mir_build/src/builder/matches/mod.rs
+++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs
@@ -5,6 +5,11 @@
 //! This also includes code for pattern bindings in `let` statements and
 //! function parameters.
 
+use std::assert_matches::assert_matches;
+use std::borrow::Borrow;
+use std::mem;
+use std::sync::Arc;
+
 use rustc_abi::VariantIdx;
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_data_structures::stack::ensure_sufficient_stack;
@@ -19,6 +24,7 @@ use tracing::{debug, instrument};
 
 use crate::builder::ForGuard::{self, OutsideGuard, RefWithinGuard};
 use crate::builder::expr::as_place::PlaceBuilder;
+use crate::builder::matches::user_ty::ProjectedUserTypesNode;
 use crate::builder::scope::DropKind;
 use crate::builder::{
     BlockAnd, BlockAndExtension, Builder, GuardFrame, GuardFrameLocal, LocalsForNode,
@@ -27,13 +33,9 @@ use crate::builder::{
 // helper functions, broken out by category:
 mod match_pair;
 mod test;
+mod user_ty;
 mod util;
 
-use std::assert_matches::assert_matches;
-use std::borrow::Borrow;
-use std::mem;
-use std::sync::Arc;
-
 /// Arguments to [`Builder::then_else_break_inner`] that are usually forwarded
 /// to recursive invocations.
 #[derive(Clone, Copy)]
@@ -757,11 +759,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
     ) -> Option<SourceScope> {
         self.visit_primary_bindings_special(
             pattern,
-            UserTypeProjections::none(),
-            &mut |this, name, mode, var, span, ty, user_ty| {
+            &ProjectedUserTypesNode::None,
+            &mut |this, name, mode, var, span, ty, user_tys| {
                 let vis_scope = *visibility_scope
                     .get_or_insert_with(|| this.new_source_scope(scope_span, LintLevel::Inherited));
                 let source_info = SourceInfo { span, scope: this.source_scope };
+                let user_tys = user_tys.build_user_type_projections();
 
                 this.declare_binding(
                     source_info,
@@ -770,7 +773,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                     mode,
                     var,
                     ty,
-                    user_ty,
+                    user_tys,
                     ArmHasGuard(guard.is_some()),
                     opt_match_place.map(|(x, y)| (x.cloned(), y)),
                     pattern.span,
@@ -874,7 +877,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
     fn visit_primary_bindings_special(
         &mut self,
         pattern: &Pat<'tcx>,
-        pattern_user_ty: UserTypeProjections,
+        user_tys: &ProjectedUserTypesNode<'_>,
         f: &mut impl FnMut(
             &mut Self,
             Symbol,
@@ -882,21 +885,21 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
             LocalVarId,
             Span,
             Ty<'tcx>,
-            UserTypeProjections,
+            &ProjectedUserTypesNode<'_>,
         ),
     ) {
         // Avoid having to write the full method name at each recursive call.
-        let visit_subpat = |this: &mut Self, subpat, user_tys, f: &mut _| {
+        let visit_subpat = |this: &mut Self, subpat, user_tys: &_, f: &mut _| {
             this.visit_primary_bindings_special(subpat, user_tys, f)
         };
 
         match pattern.kind {
             PatKind::Binding { name, mode, var, ty, ref subpattern, is_primary, .. } => {
                 if is_primary {
-                    f(self, name, mode, var, pattern.span, ty, pattern_user_ty.clone());
+                    f(self, name, mode, var, pattern.span, ty, user_tys);
                 }
                 if let Some(subpattern) = subpattern.as_ref() {
-                    visit_subpat(self, subpattern, pattern_user_ty, f);
+                    visit_subpat(self, subpattern, user_tys, f);
                 }
             }
 
@@ -905,13 +908,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 let from = u64::try_from(prefix.len()).unwrap();
                 let to = u64::try_from(suffix.len()).unwrap();
                 for subpattern in prefix.iter() {
-                    visit_subpat(self, subpattern, pattern_user_ty.clone().index(), f);
+                    visit_subpat(self, subpattern, &user_tys.index(), f);
                 }
                 if let Some(subpattern) = slice {
-                    visit_subpat(self, subpattern, pattern_user_ty.clone().subslice(from, to), f);
+                    visit_subpat(self, subpattern, &user_tys.subslice(from, to), f);
                 }
                 for subpattern in suffix.iter() {
-                    visit_subpat(self, subpattern, pattern_user_ty.clone().index(), f);
+                    visit_subpat(self, subpattern, &user_tys.index(), f);
                 }
             }
 
@@ -922,11 +925,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
             | PatKind::Error(_) => {}
 
             PatKind::Deref { ref subpattern } => {
-                visit_subpat(self, subpattern, pattern_user_ty.deref(), f);
+                visit_subpat(self, subpattern, &user_tys.deref(), f);
             }
 
             PatKind::DerefPattern { ref subpattern, .. } => {
-                visit_subpat(self, subpattern, UserTypeProjections::none(), f);
+                visit_subpat(self, subpattern, &ProjectedUserTypesNode::None, f);
             }
 
             PatKind::AscribeUserType {
@@ -942,28 +945,31 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 // Note that the variance doesn't apply here, as we are tracking the effect
                 // of `user_ty` on any bindings contained with subpattern.
 
+                // Caution: Pushing this user type here is load-bearing even for
+                // patterns containing no bindings, to ensure that the type ends
+                // up represented in MIR _somewhere_.
                 let base_user_ty = self.canonical_user_type_annotations.push(annotation.clone());
-                let subpattern_user_ty = pattern_user_ty.push_user_type(base_user_ty);
-                visit_subpat(self, subpattern, subpattern_user_ty, f)
+                let subpattern_user_tys = user_tys.push_user_type(base_user_ty);
+                visit_subpat(self, subpattern, &subpattern_user_tys, f)
             }
 
             PatKind::ExpandedConstant { ref subpattern, .. } => {
-                visit_subpat(self, subpattern, pattern_user_ty, f)
+                visit_subpat(self, subpattern, user_tys, f)
             }
 
             PatKind::Leaf { ref subpatterns } => {
                 for subpattern in subpatterns {
-                    let subpattern_user_ty = pattern_user_ty.clone().leaf(subpattern.field);
-                    debug!("visit_primary_bindings: subpattern_user_ty={:?}", subpattern_user_ty);
-                    visit_subpat(self, &subpattern.pattern, subpattern_user_ty, f);
+                    let subpattern_user_tys = user_tys.leaf(subpattern.field);
+                    debug!("visit_primary_bindings: subpattern_user_tys={subpattern_user_tys:?}");
+                    visit_subpat(self, &subpattern.pattern, &subpattern_user_tys, f);
                 }
             }
 
             PatKind::Variant { adt_def, args: _, variant_index, ref subpatterns } => {
                 for subpattern in subpatterns {
-                    let subpattern_user_ty =
-                        pattern_user_ty.clone().variant(adt_def, variant_index, subpattern.field);
-                    visit_subpat(self, &subpattern.pattern, subpattern_user_ty, f);
+                    let subpattern_user_tys =
+                        user_tys.variant(adt_def, variant_index, subpattern.field);
+                    visit_subpat(self, &subpattern.pattern, &subpattern_user_tys, f);
                 }
             }
             PatKind::Or { ref pats } => {
@@ -972,7 +978,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 // `let (x | y) = ...`, the primary binding of `y` occurs in
                 // the right subpattern
                 for subpattern in pats.iter() {
-                    visit_subpat(self, subpattern, pattern_user_ty.clone(), f);
+                    visit_subpat(self, subpattern, user_tys, f);
                 }
             }
         }
@@ -2764,7 +2770,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         mode: BindingMode,
         var_id: LocalVarId,
         var_ty: Ty<'tcx>,
-        user_ty: UserTypeProjections,
+        user_ty: Option<Box<UserTypeProjections>>,
         has_guard: ArmHasGuard,
         opt_match_place: Option<(Option<Place<'tcx>>, Span)>,
         pat_span: Span,
@@ -2774,7 +2780,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
         let local = LocalDecl {
             mutability: mode.1,
             ty: var_ty,
-            user_ty: if user_ty.is_empty() { None } else { Some(Box::new(user_ty)) },
+            user_ty,
             source_info,
             local_info: ClearCrossCrate::Set(Box::new(LocalInfo::User(BindingForm::Var(
                 VarBindingForm {
diff --git a/compiler/rustc_mir_build/src/builder/matches/user_ty.rs b/compiler/rustc_mir_build/src/builder/matches/user_ty.rs
new file mode 100644
index 0000000000000..df9f93ac328a6
--- /dev/null
+++ b/compiler/rustc_mir_build/src/builder/matches/user_ty.rs
@@ -0,0 +1,140 @@
+//! Helper code for building a linked list of user-type projections on the
+//! stack while visiting a THIR pattern.
+//!
+//! This avoids having to repeatedly clone a partly-built [`UserTypeProjections`]
+//! at every step of the traversal, which is what the previous code was doing.
+
+use std::assert_matches::assert_matches;
+use std::iter;
+
+use rustc_abi::{FieldIdx, VariantIdx};
+use rustc_middle::mir::{ProjectionElem, UserTypeProjection, UserTypeProjections};
+use rustc_middle::ty::{AdtDef, UserTypeAnnotationIndex};
+use rustc_span::Symbol;
+
+/// One of a list of "operations" that can be used to lazily build projections
+/// of user-specified types.
+#[derive(Clone, Debug)]
+pub(crate) enum ProjectedUserTypesOp {
+    PushUserType { base: UserTypeAnnotationIndex },
+
+    Index,
+    Subslice { from: u64, to: u64 },
+    Deref,
+    Leaf { field: FieldIdx },
+    Variant { name: Symbol, variant: VariantIdx, field: FieldIdx },
+}
+
+#[derive(Debug)]
+pub(crate) enum ProjectedUserTypesNode<'a> {
+    None,
+    Chain { parent: &'a Self, op: ProjectedUserTypesOp },
+}
+
+impl<'a> ProjectedUserTypesNode<'a> {
+    pub(crate) fn push_user_type(&'a self, base: UserTypeAnnotationIndex) -> Self {
+        // Pushing a base user type always causes the chain to become non-empty.
+        Self::Chain { parent: self, op: ProjectedUserTypesOp::PushUserType { base } }
+    }
+
+    /// Push another projection op onto the chain, but only if it is already non-empty.
+    fn maybe_push(&'a self, op_fn: impl FnOnce() -> ProjectedUserTypesOp) -> Self {
+        match self {
+            Self::None => Self::None,
+            Self::Chain { .. } => Self::Chain { parent: self, op: op_fn() },
+        }
+    }
+
+    pub(crate) fn index(&'a self) -> Self {
+        self.maybe_push(|| ProjectedUserTypesOp::Index)
+    }
+
+    pub(crate) fn subslice(&'a self, from: u64, to: u64) -> Self {
+        self.maybe_push(|| ProjectedUserTypesOp::Subslice { from, to })
+    }
+
+    pub(crate) fn deref(&'a self) -> Self {
+        self.maybe_push(|| ProjectedUserTypesOp::Deref)
+    }
+
+    pub(crate) fn leaf(&'a self, field: FieldIdx) -> Self {
+        self.maybe_push(|| ProjectedUserTypesOp::Leaf { field })
+    }
+
+    pub(crate) fn variant(
+        &'a self,
+        adt_def: AdtDef<'_>,
+        variant: VariantIdx,
+        field: FieldIdx,
+    ) -> Self {
+        self.maybe_push(|| {
+            let name = adt_def.variant(variant).name;
+            ProjectedUserTypesOp::Variant { name, variant, field }
+        })
+    }
+
+    /// Traverses the chain of nodes to yield each op in the chain.
+    /// Because this walks from child node to parent node, the ops are
+    /// naturally yielded in "reverse" order.
+    fn iter_ops_reversed(&'a self) -> impl Iterator<Item = &'a ProjectedUserTypesOp> {
+        let mut next = self;
+        iter::from_fn(move || match next {
+            Self::None => None,
+            Self::Chain { parent, op } => {
+                next = parent;
+                Some(op)
+            }
+        })
+    }
+
+    /// Assembles this chain of user-type projections into a proper data structure.
+    pub(crate) fn build_user_type_projections(&self) -> Option<Box<UserTypeProjections>> {
+        // If we know there's nothing to do, just return None immediately.
+        if matches!(self, Self::None) {
+            return None;
+        }
+
+        let ops_reversed = self.iter_ops_reversed().cloned().collect::<Vec<_>>();
+        // The "first" op should always be `PushUserType`.
+        // Other projections are only added if there is at least one user type.
+        assert_matches!(ops_reversed.last(), Some(ProjectedUserTypesOp::PushUserType { .. }));
+
+        let mut projections = vec![];
+        for op in ops_reversed.into_iter().rev() {
+            match op {
+                ProjectedUserTypesOp::PushUserType { base } => {
+                    projections.push(UserTypeProjection { base, projs: vec![] })
+                }
+
+                ProjectedUserTypesOp::Index => {
+                    for p in &mut projections {
+                        p.projs.push(ProjectionElem::Index(()))
+                    }
+                }
+                ProjectedUserTypesOp::Subslice { from, to } => {
+                    for p in &mut projections {
+                        p.projs.push(ProjectionElem::Subslice { from, to, from_end: true })
+                    }
+                }
+                ProjectedUserTypesOp::Deref => {
+                    for p in &mut projections {
+                        p.projs.push(ProjectionElem::Deref)
+                    }
+                }
+                ProjectedUserTypesOp::Leaf { field } => {
+                    for p in &mut projections {
+                        p.projs.push(ProjectionElem::Field(field, ()))
+                    }
+                }
+                ProjectedUserTypesOp::Variant { name, variant, field } => {
+                    for p in &mut projections {
+                        p.projs.push(ProjectionElem::Downcast(Some(name), variant));
+                        p.projs.push(ProjectionElem::Field(field, ()));
+                    }
+                }
+            }
+        }
+
+        Some(Box::new(UserTypeProjections { contents: projections }))
+    }
+}