Merge pull request #20273 from ShoyuVanilla/match-adjusts

fix: Apply adjusts to pats and exprs when doing pat analysis
This commit is contained in:
Chayim Refael Friedman 2025-07-21 16:31:09 +00:00 committed by GitHub
commit 9a1ee18e4d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 95 additions and 49 deletions

View File

@ -175,8 +175,9 @@ impl ExprValidator {
}); });
} }
let receiver_ty = self.infer[*receiver].clone(); if let Some(receiver_ty) = self.infer.type_of_expr_with_adjust(*receiver) {
checker.prev_receiver_ty = Some(receiver_ty); checker.prev_receiver_ty = Some(receiver_ty.clone());
}
} }
} }
@ -187,7 +188,9 @@ impl ExprValidator {
arms: &[MatchArm], arms: &[MatchArm],
db: &dyn HirDatabase, db: &dyn HirDatabase,
) { ) {
let scrut_ty = &self.infer[scrutinee_expr]; let Some(scrut_ty) = self.infer.type_of_expr_with_adjust(scrutinee_expr) else {
return;
};
if scrut_ty.contains_unknown() { if scrut_ty.contains_unknown() {
return; return;
} }
@ -200,7 +203,7 @@ impl ExprValidator {
// Note: Skipping the entire diagnostic rather than just not including a faulty match arm is // Note: Skipping the entire diagnostic rather than just not including a faulty match arm is
// preferred to avoid the chance of false positives. // preferred to avoid the chance of false positives.
for arm in arms { for arm in arms {
let Some(pat_ty) = self.infer.type_of_pat.get(arm.pat) else { let Some(pat_ty) = self.infer.type_of_pat_with_adjust(arm.pat) else {
return; return;
}; };
if pat_ty.contains_unknown() { if pat_ty.contains_unknown() {
@ -328,7 +331,7 @@ impl ExprValidator {
continue; continue;
} }
let Some(initializer) = initializer else { continue }; let Some(initializer) = initializer else { continue };
let ty = &self.infer[initializer]; let Some(ty) = self.infer.type_of_expr_with_adjust(initializer) else { continue };
if ty.contains_unknown() { if ty.contains_unknown() {
continue; continue;
} }
@ -433,44 +436,44 @@ impl ExprValidator {
Statement::Expr { expr, .. } => Some(*expr), Statement::Expr { expr, .. } => Some(*expr),
_ => None, _ => None,
}); });
if let Some(last_then_expr) = last_then_expr { if let Some(last_then_expr) = last_then_expr
let last_then_expr_ty = &self.infer[last_then_expr]; && let Some(last_then_expr_ty) =
if last_then_expr_ty.is_never() { self.infer.type_of_expr_with_adjust(last_then_expr)
// Only look at sources if the then branch diverges and we have an else branch. && last_then_expr_ty.is_never()
let source_map = db.body_with_source_map(self.owner).1; {
let Ok(source_ptr) = source_map.expr_syntax(id) else { // Only look at sources if the then branch diverges and we have an else branch.
return; let source_map = db.body_with_source_map(self.owner).1;
}; let Ok(source_ptr) = source_map.expr_syntax(id) else {
let root = source_ptr.file_syntax(db); return;
let either::Left(ast::Expr::IfExpr(if_expr)) = };
source_ptr.value.to_node(&root) let root = source_ptr.file_syntax(db);
else { let either::Left(ast::Expr::IfExpr(if_expr)) = source_ptr.value.to_node(&root)
return; else {
}; return;
let mut top_if_expr = if_expr; };
loop { let mut top_if_expr = if_expr;
let parent = top_if_expr.syntax().parent(); loop {
let has_parent_expr_stmt_or_stmt_list = let parent = top_if_expr.syntax().parent();
parent.as_ref().is_some_and(|node| { let has_parent_expr_stmt_or_stmt_list =
ast::ExprStmt::can_cast(node.kind()) parent.as_ref().is_some_and(|node| {
| ast::StmtList::can_cast(node.kind()) ast::ExprStmt::can_cast(node.kind())
}); | ast::StmtList::can_cast(node.kind())
if has_parent_expr_stmt_or_stmt_list { });
// Only emit diagnostic if parent or direct ancestor is either if has_parent_expr_stmt_or_stmt_list {
// an expr stmt or a stmt list. // Only emit diagnostic if parent or direct ancestor is either
break; // an expr stmt or a stmt list.
} break;
let Some(parent_if_expr) = parent.and_then(ast::IfExpr::cast) else {
// Bail if parent is neither an if expr, an expr stmt nor a stmt list.
return;
};
// Check parent if expr.
top_if_expr = parent_if_expr;
} }
let Some(parent_if_expr) = parent.and_then(ast::IfExpr::cast) else {
self.diagnostics // Bail if parent is neither an if expr, an expr stmt nor a stmt list.
.push(BodyValidationDiagnostic::RemoveUnnecessaryElse { if_expr: id }) return;
};
// Check parent if expr.
top_if_expr = parent_if_expr;
} }
self.diagnostics
.push(BodyValidationDiagnostic::RemoveUnnecessaryElse { if_expr: id })
} }
} }
} }

View File

@ -561,6 +561,32 @@ impl InferenceResult {
ExprOrPatId::PatId(id) => self.type_of_pat.get(id), ExprOrPatId::PatId(id) => self.type_of_pat.get(id),
} }
} }
pub fn type_of_expr_with_adjust(&self, id: ExprId) -> Option<&Ty> {
match self.expr_adjustments.get(&id).and_then(|adjustments| {
adjustments
.iter()
.filter(|adj| {
// https://github.com/rust-lang/rust/blob/67819923ac8ea353aaa775303f4c3aacbf41d010/compiler/rustc_mir_build/src/thir/cx/expr.rs#L140
!matches!(
adj,
Adjustment {
kind: Adjust::NeverToAny,
target,
} if target.is_never()
)
})
.next_back()
}) {
Some(adjustment) => Some(&adjustment.target),
None => self.type_of_expr.get(id),
}
}
pub fn type_of_pat_with_adjust(&self, id: PatId) -> Option<&Ty> {
match self.pat_adjustments.get(&id).and_then(|adjustments| adjustments.last()) {
adjusted @ Some(_) => adjusted,
None => self.type_of_pat.get(id),
}
}
pub fn is_erroneous(&self) -> bool { pub fn is_erroneous(&self) -> bool {
self.has_errors && self.type_of_expr.iter().count() == 0 self.has_errors && self.type_of_expr.iter().count() == 0
} }

View File

@ -441,7 +441,7 @@ impl<'db> SourceAnalyzer<'db> {
) -> Option<GenericSubstitution<'db>> { ) -> Option<GenericSubstitution<'db>> {
let body = self.store()?; let body = self.store()?;
if let Expr::Field { expr: object_expr, name: _ } = body[field_expr] { if let Expr::Field { expr: object_expr, name: _ } = body[field_expr] {
let (adt, subst) = type_of_expr_including_adjust(infer, object_expr)?.as_adt()?; let (adt, subst) = infer.type_of_expr_with_adjust(object_expr)?.as_adt()?;
return Some(GenericSubstitution::new( return Some(GenericSubstitution::new(
adt.into(), adt.into(),
subst.clone(), subst.clone(),
@ -1780,10 +1780,3 @@ pub(crate) fn name_hygiene(db: &dyn HirDatabase, name: InFile<&SyntaxNode>) -> H
let ctx = span_map.span_at(name.value.text_range().start()).ctx; let ctx = span_map.span_at(name.value.text_range().start()).ctx;
HygieneId::new(ctx.opaque_and_semitransparent(db)) HygieneId::new(ctx.opaque_and_semitransparent(db))
} }
fn type_of_expr_including_adjust(infer: &InferenceResult, id: ExprId) -> Option<&Ty> {
match infer.expr_adjustment(id).and_then(|adjustments| adjustments.last()) {
Some(adjustment) => Some(&adjustment.target),
None => Some(&infer[id]),
}
}

View File

@ -131,4 +131,28 @@ fn foo(v: Enum<()>) {
"#, "#,
); );
} }
#[test]
fn regression_20259() {
check_diagnostics(
r#"
//- minicore: deref
use core::ops::Deref;
struct Foo<T>(T);
impl<T> Deref for Foo<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
fn test(x: Foo<(i32, bool)>) {
let (_a, _b): &(i32, bool) = &x;
}
"#,
);
}
} }