Unnamed repository; edit this file 'description' to name the repository.
fix: Apply adjustments to proper expr when invoking `CoerceMany`
Shoyu Vanilla 2025-02-08
parent 039ac84 · commit 11ffa88
-rw-r--r--crates/hir-ty/src/infer.rs24
-rw-r--r--crates/hir-ty/src/infer/coerce.rs38
-rw-r--r--crates/hir-ty/src/mir/eval/tests.rs33
-rw-r--r--crates/hir-ty/src/tests/coercion.rs3
4 files changed, 89 insertions, 9 deletions
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index 617ebba881..9f20117bf1 100644
--- a/crates/hir-ty/src/infer.rs
+++ b/crates/hir-ty/src/infer.rs
@@ -1239,7 +1239,29 @@ impl<'a> InferenceContext<'a> {
}
fn write_expr_adj(&mut self, expr: ExprId, adjustments: Vec<Adjustment>) {
- self.result.expr_adjustments.insert(expr, adjustments);
+ if adjustments.is_empty() {
+ return;
+ }
+ match self.result.expr_adjustments.entry(expr) {
+ std::collections::hash_map::Entry::Occupied(mut entry) => {
+ match (&mut entry.get_mut()[..], &adjustments[..]) {
+ (
+ [Adjustment { kind: Adjust::NeverToAny, target }],
+ [.., Adjustment { target: new_target, .. }],
+ ) => {
+ // NeverToAny coercion can target any type, so instead of adding a new
+ // adjustment on top we can change the target.
+ *target = new_target.clone();
+ }
+ _ => {
+ *entry.get_mut() = adjustments;
+ }
+ }
+ }
+ std::collections::hash_map::Entry::Vacant(entry) => {
+ entry.insert(adjustments);
+ }
+ }
}
fn write_method_resolution(&mut self, expr: ExprId, func: FunctionId, subst: Substitution) {
diff --git a/crates/hir-ty/src/infer/coerce.rs b/crates/hir-ty/src/infer/coerce.rs
index d40816ba8c..b490a31f68 100644
--- a/crates/hir-ty/src/infer/coerce.rs
+++ b/crates/hir-ty/src/infer/coerce.rs
@@ -163,10 +163,27 @@ impl CoerceMany {
// type is a type variable and the new one is `!`, trying it the other
// way around first would mean we make the type variable `!`, instead of
// just marking it as possibly diverging.
- if let Ok(res) = ctx.coerce(expr, &expr_ty, &self.merged_ty(), CoerceNever::Yes) {
- self.final_ty = Some(res);
- } else if let Ok(res) = ctx.coerce(expr, &self.merged_ty(), &expr_ty, CoerceNever::Yes) {
+ //
+ // - [Comment from rustc](https://github.com/rust-lang/rust/blob/5ff18d0eaefd1bd9ab8ec33dab2404a44e7631ed/compiler/rustc_hir_typeck/src/coercion.rs#L1334-L1335)
+ // First try to coerce the new expression to the type of the previous ones,
+ // but only if the new expression has no coercion already applied to it.
+ if expr.is_none_or(|expr| !ctx.result.expr_adjustments.contains_key(&expr)) {
+ if let Ok(res) = ctx.coerce(expr, &expr_ty, &self.merged_ty(), CoerceNever::Yes) {
+ self.final_ty = Some(res);
+ if let Some(expr) = expr {
+ self.expressions.push(expr);
+ }
+ return;
+ }
+ }
+
+ if let Ok((adjustments, res)) =
+ ctx.coerce_inner(&self.merged_ty(), &expr_ty, CoerceNever::Yes)
+ {
self.final_ty = Some(res);
+ for &e in &self.expressions {
+ ctx.write_expr_adj(e, adjustments.clone());
+ }
} else {
match cause {
CoercionCause::Expr(id) => {
@@ -244,14 +261,23 @@ impl InferenceContext<'_> {
// between places and values.
coerce_never: CoerceNever,
) -> Result<Ty, TypeError> {
- let from_ty = self.resolve_ty_shallow(from_ty);
- let to_ty = self.resolve_ty_shallow(to_ty);
- let (adjustments, ty) = self.table.coerce(&from_ty, &to_ty, coerce_never)?;
+ let (adjustments, ty) = self.coerce_inner(from_ty, to_ty, coerce_never)?;
if let Some(expr) = expr {
self.write_expr_adj(expr, adjustments);
}
Ok(ty)
}
+
+ fn coerce_inner(
+ &mut self,
+ from_ty: &Ty,
+ to_ty: &Ty,
+ coerce_never: CoerceNever,
+ ) -> Result<(Vec<Adjustment>, Ty), TypeError> {
+ let from_ty = self.resolve_ty_shallow(from_ty);
+ let to_ty = self.resolve_ty_shallow(to_ty);
+ self.table.coerce(&from_ty, &to_ty, coerce_never)
+ }
}
impl InferenceTable<'_> {
diff --git a/crates/hir-ty/src/mir/eval/tests.rs b/crates/hir-ty/src/mir/eval/tests.rs
index f1e86daea2..9625ae5f88 100644
--- a/crates/hir-ty/src/mir/eval/tests.rs
+++ b/crates/hir-ty/src/mir/eval/tests.rs
@@ -912,3 +912,36 @@ fn main() {
"",
);
}
+
+#[test]
+fn regression_19021() {
+ check_pass(
+ r#"
+//- minicore: deref
+use core::ops::Deref;
+
+#[lang = "owned_box"]
+struct Box<T>(T);
+
+impl<T> Deref for Box<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+struct Foo;
+
+fn main() {
+ let x = Box(Foo);
+ let y = &Foo;
+
+ || match x {
+ ref x => x,
+ _ => y,
+ };
+}
+"#,
+ );
+}
diff --git a/crates/hir-ty/src/tests/coercion.rs b/crates/hir-ty/src/tests/coercion.rs
index 7992f1feee..7e7c1f835c 100644
--- a/crates/hir-ty/src/tests/coercion.rs
+++ b/crates/hir-ty/src/tests/coercion.rs
@@ -185,11 +185,10 @@ fn test() {
let t = &mut 1;
let x = match 1 {
1 => t as *mut i32,
+ //^^^^^^^^^^^^^ adjustments: Pointer(MutToConstPointer)
2 => t as &i32,
//^^^^^^^^^ expected *mut i32, got &'? i32
_ => t as *const i32,
- // ^^^^^^^^^^^^^^^ adjustments: Pointer(MutToConstPointer)
-
};
x;
//^ type: *const i32