-
Notifications
You must be signed in to change notification settings - Fork 14.1k
Fix check_expr_if to point to a more accurate location of the compilation error in some cases #147484
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
This comment has been minimized.
This comment has been minimized.
This comment was marked as outdated.
This comment was marked as outdated.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
f309ba8 to
98bc078
Compare
This comment has been minimized.
This comment has been minimized.
98bc078 to
c50157b
Compare
This comment has been minimized.
This comment has been minimized.
c50157b to
b522404
Compare
5212a66 to
ebfaecc
Compare
…ation error in some cases
ebfaecc to
5c38b51
Compare
There was a problem hiding this comment.
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 ())
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The expected … found … label in this case isn’t new with this PR. I saw that current nightly already emits expected integer, found () for error[E0317]: if may be missing an else clause. e.g. tests/ui/expr/if/issue-4201.rs
Should this be discussed or fixed in another issue?
|
r? @SparrowLii rustbot has assigned @SparrowLii. Use |
|
|
r? types |
|
Sorry for the delay here. Before I do an actual review here, I want to check perf. @bors2 try @rust-timer build |
|
Cannot parse build command: Missing SHA in build command |
This comment has been minimized.
This comment has been minimized.
Fix check_expr_if to point to a more accurate location of the compilation error in some cases
|
@rust-timer build 9877dbd |
This comment has been minimized.
This comment has been minimized.
|
Finished benchmarking commit (9877dbd): comparison URL. Overall result: ✅ improvements - no action neededBenchmarking 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 Instruction countOur most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.
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.
CyclesResults (secondary 1.7%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Binary sizeResults (primary 0.0%, secondary 0.1%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Bootstrap: 474.307s -> 475.954s (0.35%) |
There was a problem hiding this 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.
| .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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this actually ever going to be none?
Yes, the new flattening approach collects all branches first, then processes them sequentially. During collection, we only record types for branch bodies, not the if-expression nodes themselves.
previous never bug!ed.
That is because the old implementation processed if-else chains recursively (depth-first).
when else_expr is an else-if node:
- Recursively calls check_expr_if(else_expr)
- Returns the computed type
- write_ty records it immediately
- Error reporting finds the type
The new implementation skips check_expr_with_expectation on intermediate if-nodes during collection, so their types aren't recorded yet when early errors occur.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, so...is this ever going to return Some?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed this borrowing suggestions in this commit!
fix: early returns suggest_deref_or_ref when the expr is an empty block
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed this borrowing suggestions in this commit!
fix: early returns suggest_deref_or_ref when the expr is an empty block
There was a problem hiding this 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.
compiler/rustc_hir_typeck/src/_if.rs
Outdated
| 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed those methods in this commit!
refactor: remove set_diverges, reset_diverges_to_maybe, take_diverges…
There was a problem hiding this comment.
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 🙇
compiler/rustc_hir_typeck/src/_if.rs
Outdated
| 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; | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 …
…, explain_if_branch_mismatch
2c15958 to
682541b
Compare
|
@jackh726 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some more review comments. Looking better, but still have some questions/thoughts.
| if let hir::ExprKind::Block(block, _) = &expr.kind | ||
| && block.expr.is_none() | ||
| { | ||
| return None; | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you add this? Is there a test difference that you can comment and link to?
| &self, | ||
| expr_id: HirId, | ||
| sp: Span, | ||
| parts: &IfExprParts<'tcx>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here, and elsewhere in the file, these are Copy and so shouldn't be passed by ref
| parts: &IfExprParts<'tcx>, | ||
| orig_expected: Expectation<'tcx>, | ||
| ) -> Ty<'tcx> { | ||
| let root_if_expr = self.tcx.hir_expect_expr(expr_id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could pass this in instead of expr_id and sp
| .get(idx + 1) | ||
| .map(|next| next.expr_with_parts.expr) | ||
| .or(chain.last_expr().into()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, this doesn't seem right (or at least, is too subtle for my liking): when idx is len-1, chain.last_expr() is called and there is no final else, uses the current expr (since it is the last one).
| self.misc(branch_body.span) | ||
| }; | ||
| let cause_span = | ||
| if idx == 0 { Some(root_if_expr.span) } else { Some(branch_body.span) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If idx == 0, isn't branch_body.span == root_if_expr.span?
| .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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, so...is this ever going to return Some?
|
@rustbot author |
|
Reminder, once the PR becomes ready for a review, use |
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.
Simplified Expr::If structure for this code is like below.
Current check_expr_if processes this Expr::If by depth-first-search like approach, that is why "
ifandelsehave 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