Skip to content

Commit c39fdc4

Browse files
Rollup merge of rust-lang#141003 - clubby789:ternary-improve, r=compiler-errors
Improve ternary operator recovery This - Improves the span of the error to not point at the next token - Where possible, we use the span of the condition to further improve the span of the error to include the cond, and suggest a maybe-incorrect fix Currently this works on free expressions, not let statements; some more refactoring would be needed to pass the span down, which I'm not sure is worth doing. ### Old ![image](https://github.com/user-attachments/assets/5688cefc-e4ef-4135-a5ba-340ce05ae6f3) ### New ![image](https://github.com/user-attachments/assets/154f5380-e0c8-42c7-9bf8-0adb3d0433fa)
2 parents 2686217 + 1267333 commit c39fdc4

File tree

6 files changed

+75
-17
lines changed

6 files changed

+75
-17
lines changed

compiler/rustc_parse/messages.ftl

+2-1
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,6 @@ parse_switch_ref_box_order = switch the order of `ref` and `box`
815815
.suggestion = swap them
816816
817817
parse_ternary_operator = Rust has no ternary operator
818-
.help = use an `if-else` expression instead
819818
820819
parse_tilde_is_not_unary_operator = `~` cannot be used as a unary operator
821820
.suggestion = use `!` to perform bitwise not
@@ -963,6 +962,8 @@ parse_use_empty_block_not_semi = expected { "`{}`" }, found `;`
963962
parse_use_eq_instead = unexpected `==`
964963
.suggestion = try using `=` instead
965964
965+
parse_use_if_else = use an `if-else` expression instead
966+
966967
parse_use_let_not_auto = write `let` instead of `auto` to introduce a new variable
967968
parse_use_let_not_var = write `let` instead of `var` to introduce a new variable
968969

compiler/rustc_parse/src/errors.rs

+19-1
Original file line numberDiff line numberDiff line change
@@ -436,10 +436,28 @@ pub(crate) enum IfExpressionMissingThenBlockSub {
436436

437437
#[derive(Diagnostic)]
438438
#[diag(parse_ternary_operator)]
439-
#[help]
440439
pub(crate) struct TernaryOperator {
441440
#[primary_span]
442441
pub span: Span,
442+
/// If we have a span for the condition expression, suggest the if/else
443+
#[subdiagnostic]
444+
pub sugg: Option<TernaryOperatorSuggestion>,
445+
/// Otherwise, just print the suggestion message
446+
#[help(parse_use_if_else)]
447+
pub no_sugg: bool,
448+
}
449+
450+
#[derive(Subdiagnostic, Copy, Clone)]
451+
#[multipart_suggestion(parse_use_if_else, applicability = "maybe-incorrect", style = "verbose")]
452+
pub(crate) struct TernaryOperatorSuggestion {
453+
#[suggestion_part(code = "if ")]
454+
pub before_cond: Span,
455+
#[suggestion_part(code = "{{")]
456+
pub question: Span,
457+
#[suggestion_part(code = "}} else {{")]
458+
pub colon: Span,
459+
#[suggestion_part(code = " }}")]
460+
pub end: Span,
443461
}
444462

445463
#[derive(Subdiagnostic)]

compiler/rustc_parse/src/parser/diagnostics.rs

+25-9
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ use crate::errors::{
4141
IncorrectSemicolon, IncorrectUseOfAwait, IncorrectUseOfUse, PatternMethodParamWithoutBody,
4242
QuestionMarkInType, QuestionMarkInTypeSugg, SelfParamNotFirst, StructLiteralBodyWithoutPath,
4343
StructLiteralBodyWithoutPathSugg, SuggAddMissingLetStmt, SuggEscapeIdentifier, SuggRemoveComma,
44-
TernaryOperator, UnexpectedConstInGenericParam, UnexpectedConstParamDeclaration,
45-
UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets, UseEqInstead, WrapType,
44+
TernaryOperator, TernaryOperatorSuggestion, UnexpectedConstInGenericParam,
45+
UnexpectedConstParamDeclaration, UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets,
46+
UseEqInstead, WrapType,
4647
};
4748
use crate::parser::attr::InnerAttrPolicy;
4849
use crate::{exp, fluent_generated as fluent};
@@ -497,7 +498,7 @@ impl<'a> Parser<'a> {
497498
// If the user is trying to write a ternary expression, recover it and
498499
// return an Err to prevent a cascade of irrelevant diagnostics.
499500
if self.prev_token == token::Question
500-
&& let Err(e) = self.maybe_recover_from_ternary_operator()
501+
&& let Err(e) = self.maybe_recover_from_ternary_operator(None)
501502
{
502503
return Err(e);
503504
}
@@ -1602,12 +1603,18 @@ impl<'a> Parser<'a> {
16021603
/// Rust has no ternary operator (`cond ? then : else`). Parse it and try
16031604
/// to recover from it if `then` and `else` are valid expressions. Returns
16041605
/// an err if this appears to be a ternary expression.
1605-
pub(super) fn maybe_recover_from_ternary_operator(&mut self) -> PResult<'a, ()> {
1606+
/// If we have the span of the condition, we can provide a better error span
1607+
/// and code suggestion.
1608+
pub(super) fn maybe_recover_from_ternary_operator(
1609+
&mut self,
1610+
cond: Option<Span>,
1611+
) -> PResult<'a, ()> {
16061612
if self.prev_token != token::Question {
16071613
return PResult::Ok(());
16081614
}
16091615

1610-
let lo = self.prev_token.span.lo();
1616+
let question = self.prev_token.span;
1617+
let lo = cond.unwrap_or(question).lo();
16111618
let snapshot = self.create_snapshot_for_diagnostic();
16121619

16131620
if match self.parse_expr() {
@@ -1620,11 +1627,20 @@ impl<'a> Parser<'a> {
16201627
}
16211628
} {
16221629
if self.eat_noexpect(&token::Colon) {
1630+
let colon = self.prev_token.span;
16231631
match self.parse_expr() {
1624-
Ok(_) => {
1625-
return Err(self
1626-
.dcx()
1627-
.create_err(TernaryOperator { span: self.token.span.with_lo(lo) }));
1632+
Ok(expr) => {
1633+
let sugg = cond.map(|cond| TernaryOperatorSuggestion {
1634+
before_cond: cond.shrink_to_lo(),
1635+
question,
1636+
colon,
1637+
end: expr.span.shrink_to_hi(),
1638+
});
1639+
return Err(self.dcx().create_err(TernaryOperator {
1640+
span: self.prev_token.span.with_lo(lo),
1641+
sugg,
1642+
no_sugg: sugg.is_none(),
1643+
}));
16281644
}
16291645
Err(err) => {
16301646
err.cancel();

compiler/rustc_parse/src/parser/stmt.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -879,7 +879,12 @@ impl<'a> Parser<'a> {
879879
{
880880
// Just check for errors and recover; do not eat semicolon yet.
881881

882-
let expect_result = self.expect_one_of(&[], &[exp!(Semi), exp!(CloseBrace)]);
882+
let expect_result =
883+
if let Err(e) = self.maybe_recover_from_ternary_operator(Some(expr.span)) {
884+
Err(e)
885+
} else {
886+
self.expect_one_of(&[], &[exp!(Semi), exp!(CloseBrace)])
887+
};
883888

884889
// Try to both emit a better diagnostic, and avoid further errors by replacing
885890
// the `expr` with `ExprKind::Err`.

tests/ui/parser/ternary_operator.rs

+6
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,9 @@ fn main() {
2828
//~| HELP use an `if-else` expression instead
2929
//~| ERROR expected one of `.`, `;`, `?`, `else`, or an operator, found `:`
3030
}
31+
32+
fn expr(a: u64, b: u64) -> u64 {
33+
a > b ? a : b
34+
//~^ ERROR Rust has no ternary operator
35+
//~| HELP use an `if-else` expression instead
36+
}

tests/ui/parser/ternary_operator.stderr

+17-5
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,23 @@ error: Rust has no ternary operator
22
--> $DIR/ternary_operator.rs:2:19
33
|
44
LL | let x = 5 > 2 ? true : false;
5-
| ^^^^^^^^^^^^^^^
5+
| ^^^^^^^^^^^^^^
66
|
77
= help: use an `if-else` expression instead
88

99
error: Rust has no ternary operator
1010
--> $DIR/ternary_operator.rs:8:19
1111
|
1212
LL | let x = 5 > 2 ? { true } : { false };
13-
| ^^^^^^^^^^^^^^^^^^^^^^^
13+
| ^^^^^^^^^^^^^^^^^^^^^^
1414
|
1515
= help: use an `if-else` expression instead
1616

1717
error: Rust has no ternary operator
1818
--> $DIR/ternary_operator.rs:14:19
1919
|
2020
LL | let x = 5 > 2 ? f32::MAX : f32::MIN;
21-
| ^^^^^^^^^^^^^^^^^^^^^^
21+
| ^^^^^^^^^^^^^^^^^^^^^
2222
|
2323
= help: use an `if-else` expression instead
2424

@@ -38,9 +38,21 @@ error: Rust has no ternary operator
3838
--> $DIR/ternary_operator.rs:26:19
3939
|
4040
LL | let x = 5 > 2 ? { let x = vec![]: Vec<u16>; x } : { false };
41-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
41+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4242
|
4343
= help: use an `if-else` expression instead
4444

45-
error: aborting due to 6 previous errors
45+
error: Rust has no ternary operator
46+
--> $DIR/ternary_operator.rs:33:5
47+
|
48+
LL | a > b ? a : b
49+
| ^^^^^^^^^^^^^
50+
|
51+
help: use an `if-else` expression instead
52+
|
53+
LL - a > b ? a : b
54+
LL + if a > b { a } else { b }
55+
|
56+
57+
error: aborting due to 7 previous errors
4658

0 commit comments

Comments
 (0)