Skip to content

Conversation

@sgasho
Copy link
Contributor

@sgasho sgasho commented Oct 8, 2025

closes #146190

The problem linked on the issue above lays on how Expr::If is processed.

Currently, Expr::If is processed by depth-first like approach, which is natural considering that Expr::If is a nested structure.

e.g.

fn main() {
    let oa = Some(1);
    let oa2 = Some(1);
    let v = if let Some(a) = oa {
        Some(&a)
    } else if let Some(a) = oa2 {
        &Some(a)
    } else {
        None
    };
    println!("{v:?}");
}

Simplified Expr::If structure for this code is like below.

Expr::If {
  cond: "if let Some(a) = oa",
  then: "Some(&a)",
  else: {
    Expr::If {
      cond: "else if let Some(a) = oa2",
      then: "&Some(a)",
      else: {
        cond: "else",
        then: "None",
        else: None,
      }
    }
  }
}

Current check_expr_if processes this Expr::If by depth-first-search like approach, that is why "if and else have incompatible types" occurs between else if let Some(a) = oa2 and else, not between if let Some(a) = oa and if let Some(a) = oa2.

New approach on this PR is first flatten the Expr::If nested nodes, then process forward.

I created _if.rs and moved the check_expr_if becase this new approach requires a bit more lines of codes.

I added a test case that guarantees the problem on the linked issue has been resolved.
tests/ui/expr/if/if-multi-else-if-incompatible-types-issue-146190.rs

Discussion on Zulip: https://rust-lang.zulipchat.com/#narrow/channel/182449-t-compiler.2Fhelp/topic/Struggling.20to.20fix.20issue.20.23146190.20check_expr_if/with/540960074

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Oct 8, 2025
@sgasho

This comment was marked as outdated.

@rust-log-analyzer

This comment has been minimized.

@sgasho

This comment was marked as outdated.

@sgasho sgasho changed the title needs_help: wip fix 146190 Fix check_expr_if to point to a more accurate location of the compilation error in some cases Oct 10, 2025
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@sgasho sgasho force-pushed the fix/141690_check_expr_if branch from f309ba8 to 98bc078 Compare October 13, 2025 01:55
@rust-log-analyzer

This comment has been minimized.

@sgasho sgasho force-pushed the fix/141690_check_expr_if branch from 98bc078 to c50157b Compare October 13, 2025 02:24
@rust-log-analyzer

This comment has been minimized.

@sgasho sgasho force-pushed the fix/141690_check_expr_if branch from c50157b to b522404 Compare October 13, 2025 06:52
@rustbot rustbot added the T-clippy Relevant to the Clippy team. label Oct 13, 2025
@sgasho sgasho force-pushed the fix/141690_check_expr_if branch 3 times, most recently from 5212a66 to ebfaecc Compare October 14, 2025 15:41
@sgasho sgasho force-pushed the fix/141690_check_expr_if branch from ebfaecc to 5c38b51 Compare October 15, 2025 13:39
Comment on lines 12 to 17
Copy link
Contributor Author

@sgasho sgasho Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was "if and else (have incompatible types) " appropriate for this case?
I think this else if branch has expression of type ! and it is compatible for any type. (source: "Expressions of type ! can be coerced into any other type." at https://doc.rust-lang.org/reference/types/never.html ).

I could be wrong but my understanding is showing "if may be missing an else clause" for this case (all if-else-if have compatible types and the only problem is missing else branch, which would be evaluated to ())

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So,

Hmm, well, I wouldn't expect the incompatible types error. But I also wouldn't expect to see an expected .. found ... here.

I guess, the found () is possibly the type of the entire if/else if expression.

@sgasho sgasho marked this pull request as ready for review October 15, 2025 15:06
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Oct 15, 2025
@rustbot rustbot removed the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Oct 15, 2025
@rustbot
Copy link
Collaborator

rustbot commented Oct 15, 2025

r? @SparrowLii

rustbot has assigned @SparrowLii.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot
Copy link
Collaborator

rustbot commented Oct 15, 2025

⚠️ Warning ⚠️

@fmease
Copy link
Member

fmease commented Oct 19, 2025

r? types

@rustbot rustbot added the T-types Relevant to the types team, which will review and decide on the PR/issue. label Oct 19, 2025
@rustbot rustbot assigned jackh726 and unassigned SparrowLii Oct 19, 2025
@jackh726
Copy link
Member

Sorry for the delay here. Before I do an actual review here, I want to check perf.

@bors2 try @rust-timer build

@rust-timer
Copy link
Collaborator

Cannot parse build command: Missing SHA in build command

@rust-bors

This comment has been minimized.

rust-bors bot added a commit that referenced this pull request Oct 29, 2025
Fix check_expr_if to point to a more accurate location of the compilation error in some cases
@rust-bors
Copy link

rust-bors bot commented Oct 29, 2025

☀️ Try build successful (CI)
Build commit: 9877dbd (9877dbd994fb24ec6e7e51ae64b82777ed0daea4, parent: 044d68c3cb6a0b893b18293fa7f5719119403215)

@jackh726
Copy link
Member

@rust-timer build 9877dbd

@rust-timer

This comment has been minimized.

@rust-timer
Copy link
Collaborator

Finished benchmarking commit (9877dbd): comparison URL.

Overall result: ✅ improvements - no action needed

Benchmarking this pull request means it may be perf-sensitive – we'll automatically label it not fit for rolling up. You can override this, but we strongly advise not to, due to possible changes in compiler perf.

@bors rollup=never
@rustbot label: -S-waiting-on-perf -perf-regression

Instruction count

Our most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
-0.3% [-0.3%, -0.3%] 1
Improvements ✅
(secondary)
-0.1% [-0.1%, -0.1%] 2
All ❌✅ (primary) -0.3% [-0.3%, -0.3%] 1

Max RSS (memory usage)

Results (primary 0.0%, secondary -2.1%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
5.9% [5.9%, 5.9%] 1
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
-3.0% [-3.6%, -2.3%] 2
Improvements ✅
(secondary)
-2.1% [-2.1%, -2.1%] 1
All ❌✅ (primary) 0.0% [-3.6%, 5.9%] 3

Cycles

Results (secondary 1.7%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
6.5% [6.5%, 6.5%] 1
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-3.1% [-3.1%, -3.1%] 1
All ❌✅ (primary) - - 0

Binary size

Results (primary 0.0%, secondary 0.1%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
0.2% [0.1%, 0.3%] 4
Regressions ❌
(secondary)
0.1% [0.1%, 0.1%] 1
Improvements ✅
(primary)
-0.1% [-0.1%, -0.1%] 3
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) 0.0% [-0.1%, 0.3%] 7

Bootstrap: 474.307s -> 475.954s (0.35%)
Artifact size: 390.07 MiB -> 390.09 MiB (0.00%)

Copy link
Member

@jackh726 jackh726 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perf looks good, so that's good to see.

Haven't yet reviewed the meat of this, but some comments. If you want to try to address them, that's great. If you want to wait until I review the meat of this, that's okay too.

I'll try to get back to this later today or this weekend, but want to share what I have.

View changes since this review

.typeck_results
.as_ref()
.expect("if expression only expected inside FnCtxt")
.expr_ty(else_expr);
if let hir::ExprKind::If(_cond, _then, None) = else_expr.kind
.expr_ty_opt(else_expr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. This went from non-opt to opt. Is this actually ever going to be none? I guess, I would expect so, but surprised the previous never bug!ed.

Comment on lines 12 to 17
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So,

Hmm, well, I wouldn't expect the incompatible types error. But I also wouldn't expect to see an expected .. found ... here.

I guess, the found () is possibly the type of the entire if/else if expression.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, this isn't right :)

Comment on lines 14 to 17
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

Copy link
Member

@jackh726 jackh726 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, another round of review. These changes I am going to pause for you to address before I get back to this.

View changes since this review

Comment on lines 269 to 281
fn set_diverges(
&self,
initial_diverges: Diverges,
guarded: &IfGuardedBranches<'tcx>,
tail: &IfChainTail<'tcx>,
) {
let mut tail_diverges = tail.diverges();
for branch in guarded.branches.iter().rev() {
tail_diverges = branch.cond_diverges | (branch.body.diverges & tail_diverges);
}
self.diverges.set(initial_diverges | tail_diverges);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually not a fan of splitting this (along with reset_diverges_to_maybe and take_diverges) into separate functions.

I guess, the more I look over this, I also am not a huge fan of explain_if_branch_mismatch either.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will check other comments tomorrow 🙇

sp: Span,
orig_expected: Expectation<'tcx>,
) -> Ty<'tcx> {
let root_if_expr = self.tcx.hir_expect_expr(expr_id);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't particularly like the change to not take in the fields of ExprKind::If - and I think that these fields should be passed through too (to avoid the bug! path in collect_if_branch.

Instead of passing the fields separately, could just make a new struct in this module to keep things clean.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a struct to avoid the bug! in collect_if_branch

refactor: pass cond, then, else to reduce use of

Comment on lines 195 to 202
let Some(else_expr) = self.collect_if_branch(current_if, expected, &mut chain) else {
return (chain, IfChainTail::Missing(current_if));
};

if let hir::ExprKind::If(..) = else_expr.kind {
current_if = else_expr;
continue;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something seems weird to me...what is there both an optional return in collect_if_branch and a check for ExprKind::If for the kind?

I guess, perhaps, I could see some duplicate logic here - you should be able to maintain all the state in chain, I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed duplicate logics and added some extra refactorings(sorry it if is hard to check diffs....)

refactor: remove duplicate logic for checking Expr content. and some …

@jackh726 jackh726 added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Nov 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-clippy Relevant to the Clippy team. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The compilation error prompt points to the wrong location

7 participants