Skip to content

Improve ternary operator recovery #141003

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

Merged
merged 1 commit into from
May 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion compiler/rustc_parse/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,6 @@ parse_switch_ref_box_order = switch the order of `ref` and `box`
.suggestion = swap them
parse_ternary_operator = Rust has no ternary operator
.help = use an `if-else` expression instead
parse_tilde_is_not_unary_operator = `~` cannot be used as a unary operator
.suggestion = use `!` to perform bitwise not
Expand Down Expand Up @@ -963,6 +962,8 @@ parse_use_empty_block_not_semi = expected { "`{}`" }, found `;`
parse_use_eq_instead = unexpected `==`
.suggestion = try using `=` instead
parse_use_if_else = use an `if-else` expression instead
parse_use_let_not_auto = write `let` instead of `auto` to introduce a new variable
parse_use_let_not_var = write `let` instead of `var` to introduce a new variable
Expand Down
20 changes: 19 additions & 1 deletion compiler/rustc_parse/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,10 +436,28 @@ pub(crate) enum IfExpressionMissingThenBlockSub {

#[derive(Diagnostic)]
#[diag(parse_ternary_operator)]
#[help]
pub(crate) struct TernaryOperator {
#[primary_span]
pub span: Span,
/// If we have a span for the condition expression, suggest the if/else
#[subdiagnostic]
pub sugg: Option<TernaryOperatorSuggestion>,
/// Otherwise, just print the suggestion message
#[help(parse_use_if_else)]
pub no_sugg: bool,
}

#[derive(Subdiagnostic, Copy, Clone)]
#[multipart_suggestion(parse_use_if_else, applicability = "maybe-incorrect", style = "verbose")]
pub(crate) struct TernaryOperatorSuggestion {
#[suggestion_part(code = "if ")]
pub before_cond: Span,
#[suggestion_part(code = "{{")]
pub question: Span,
#[suggestion_part(code = "}} else {{")]
pub colon: Span,
#[suggestion_part(code = " }}")]
pub end: Span,
}

#[derive(Subdiagnostic)]
Expand Down
34 changes: 25 additions & 9 deletions compiler/rustc_parse/src/parser/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ use crate::errors::{
IncorrectSemicolon, IncorrectUseOfAwait, IncorrectUseOfUse, PatternMethodParamWithoutBody,
QuestionMarkInType, QuestionMarkInTypeSugg, SelfParamNotFirst, StructLiteralBodyWithoutPath,
StructLiteralBodyWithoutPathSugg, SuggAddMissingLetStmt, SuggEscapeIdentifier, SuggRemoveComma,
TernaryOperator, UnexpectedConstInGenericParam, UnexpectedConstParamDeclaration,
UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets, UseEqInstead, WrapType,
TernaryOperator, TernaryOperatorSuggestion, UnexpectedConstInGenericParam,
UnexpectedConstParamDeclaration, UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets,
UseEqInstead, WrapType,
};
use crate::parser::attr::InnerAttrPolicy;
use crate::{exp, fluent_generated as fluent};
Expand Down Expand Up @@ -497,7 +498,7 @@ impl<'a> Parser<'a> {
// If the user is trying to write a ternary expression, recover it and
// return an Err to prevent a cascade of irrelevant diagnostics.
if self.prev_token == token::Question
&& let Err(e) = self.maybe_recover_from_ternary_operator()
&& let Err(e) = self.maybe_recover_from_ternary_operator(None)
{
return Err(e);
}
Expand Down Expand Up @@ -1602,12 +1603,18 @@ impl<'a> Parser<'a> {
/// Rust has no ternary operator (`cond ? then : else`). Parse it and try
/// to recover from it if `then` and `else` are valid expressions. Returns
/// an err if this appears to be a ternary expression.
pub(super) fn maybe_recover_from_ternary_operator(&mut self) -> PResult<'a, ()> {
/// If we have the span of the condition, we can provide a better error span
/// and code suggestion.
pub(super) fn maybe_recover_from_ternary_operator(
&mut self,
cond: Option<Span>,
) -> PResult<'a, ()> {
if self.prev_token != token::Question {
return PResult::Ok(());
}

let lo = self.prev_token.span.lo();
let question = self.prev_token.span;
let lo = cond.unwrap_or(question).lo();
let snapshot = self.create_snapshot_for_diagnostic();

if match self.parse_expr() {
Expand All @@ -1620,11 +1627,20 @@ impl<'a> Parser<'a> {
}
} {
if self.eat_noexpect(&token::Colon) {
let colon = self.prev_token.span;
match self.parse_expr() {
Ok(_) => {
return Err(self
.dcx()
.create_err(TernaryOperator { span: self.token.span.with_lo(lo) }));
Ok(expr) => {
let sugg = cond.map(|cond| TernaryOperatorSuggestion {
before_cond: cond.shrink_to_lo(),
question,
colon,
end: expr.span.shrink_to_hi(),
});
return Err(self.dcx().create_err(TernaryOperator {
span: self.prev_token.span.with_lo(lo),
sugg,
no_sugg: sugg.is_none(),
}));
}
Err(err) => {
err.cancel();
Expand Down
7 changes: 6 additions & 1 deletion compiler/rustc_parse/src/parser/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -879,7 +879,12 @@ impl<'a> Parser<'a> {
{
// Just check for errors and recover; do not eat semicolon yet.

let expect_result = self.expect_one_of(&[], &[exp!(Semi), exp!(CloseBrace)]);
let expect_result =
if let Err(e) = self.maybe_recover_from_ternary_operator(Some(expr.span)) {
Err(e)
} else {
self.expect_one_of(&[], &[exp!(Semi), exp!(CloseBrace)])
};

// Try to both emit a better diagnostic, and avoid further errors by replacing
// the `expr` with `ExprKind::Err`.
Expand Down
6 changes: 6 additions & 0 deletions tests/ui/parser/ternary_operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,9 @@ fn main() {
//~| HELP use an `if-else` expression instead
//~| ERROR expected one of `.`, `;`, `?`, `else`, or an operator, found `:`
}

fn expr(a: u64, b: u64) -> u64 {
a > b ? a : b
//~^ ERROR Rust has no ternary operator
//~| HELP use an `if-else` expression instead
}
22 changes: 17 additions & 5 deletions tests/ui/parser/ternary_operator.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@ error: Rust has no ternary operator
--> $DIR/ternary_operator.rs:2:19
|
LL | let x = 5 > 2 ? true : false;
| ^^^^^^^^^^^^^^^
| ^^^^^^^^^^^^^^
|
= help: use an `if-else` expression instead

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

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

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

error: aborting due to 6 previous errors
error: Rust has no ternary operator
--> $DIR/ternary_operator.rs:33:5
|
LL | a > b ? a : b
| ^^^^^^^^^^^^^
|
help: use an `if-else` expression instead
|
LL - a > b ? a : b
LL + if a > b { a } else { b }
|

error: aborting due to 7 previous errors

Loading