From 40b1f4899a9081c0d8edf56072aaeb510b63a415 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Fri, 28 Mar 2025 18:32:12 +0100 Subject: Add the feature gate for the `super let` experiment. --- compiler/rustc_parse/src/parser/stmt.rs | 15 ++++++++++++++- compiler/rustc_parse/src/parser/token_type.rs | 4 ++++ 2 files changed, 18 insertions(+), 1 deletion(-) (limited to 'compiler/rustc_parse/src/parser') diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 97cd4d2117f..2cd09aa8959 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -73,7 +73,20 @@ impl<'a> Parser<'a> { }); } - let stmt = if self.token.is_keyword(kw::Let) { + let stmt = if self.token.is_keyword(kw::Super) && self.is_keyword_ahead(1, &[kw::Let]) { + self.collect_tokens(None, attrs, force_collect, |this, attrs| { + this.expect_keyword(exp!(Super))?; + this.psess.gated_spans.gate(sym::super_let, this.prev_token.span); + this.expect_keyword(exp!(Let))?; + let local = this.parse_local(attrs)?; // FIXME(mara): implement super let + let trailing = Trailing::from(capture_semi && this.token == token::Semi); + Ok(( + this.mk_stmt(lo.to(this.prev_token.span), StmtKind::Let(local)), + trailing, + UsePreAttrPos::No, + )) + })? + } else if self.token.is_keyword(kw::Let) { self.collect_tokens(None, attrs, force_collect, |this, attrs| { this.expect_keyword(exp!(Let))?; let local = this.parse_local(attrs)?; diff --git a/compiler/rustc_parse/src/parser/token_type.rs b/compiler/rustc_parse/src/parser/token_type.rs index 886438fd583..add3c970201 100644 --- a/compiler/rustc_parse/src/parser/token_type.rs +++ b/compiler/rustc_parse/src/parser/token_type.rs @@ -114,6 +114,7 @@ pub enum TokenType { KwSelfUpper, KwStatic, KwStruct, + KwSuper, KwTrait, KwTry, KwType, @@ -250,6 +251,7 @@ impl TokenType { KwSelfUpper, KwStatic, KwStruct, + KwSuper, KwTrait, KwTry, KwType, @@ -324,6 +326,7 @@ impl TokenType { TokenType::KwSelfUpper => Some(kw::SelfUpper), TokenType::KwStatic => Some(kw::Static), TokenType::KwStruct => Some(kw::Struct), + TokenType::KwSuper => Some(kw::Super), TokenType::KwTrait => Some(kw::Trait), TokenType::KwTry => Some(kw::Try), TokenType::KwType => Some(kw::Type), @@ -549,6 +552,7 @@ macro_rules! exp { (SelfUpper) => { exp!(@kw, SelfUpper, KwSelfUpper) }; (Static) => { exp!(@kw, Static, KwStatic) }; (Struct) => { exp!(@kw, Struct, KwStruct) }; + (Super) => { exp!(@kw, Super, KwSuper) }; (Trait) => { exp!(@kw, Trait, KwTrait) }; (Try) => { exp!(@kw, Try, KwTry) }; (Type) => { exp!(@kw, Type, KwType) }; -- cgit 1.4.1-3-g733a5 From d8d27ca82249555906f5deb687e44813beef0062 Mon Sep 17 00:00:00 2001 From: Freya Arbjerg Date: Wed, 2 Apr 2025 17:39:42 +0200 Subject: Fix two incorrect turbofish suggestions Fixes #121901 --- compiler/rustc_parse/src/parser/item.rs | 32 +++++++++++++++++----- tests/ui/fn/bad-turbofish-hints-issue-121901.rs | 8 ++++++ .../ui/fn/bad-turbofish-hints-issue-121901.stderr | 25 +++++++++++++++++ 3 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 tests/ui/fn/bad-turbofish-hints-issue-121901.rs create mode 100644 tests/ui/fn/bad-turbofish-hints-issue-121901.stderr (limited to 'compiler/rustc_parse/src/parser') diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index a8208e4717b..9405b58ab3b 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -2960,13 +2960,30 @@ impl<'a> Parser<'a> { let parser_snapshot_before_ty = this.create_snapshot_for_diagnostic(); this.eat_incorrect_doc_comment_for_param_type(); let mut ty = this.parse_ty_for_param(); - if ty.is_ok() - && this.token != token::Comma - && this.token != token::CloseDelim(Delimiter::Parenthesis) - { - // This wasn't actually a type, but a pattern looking like a type, - // so we are going to rollback and re-parse for recovery. - ty = this.unexpected_any(); + + if let Ok(t) = &ty { + // Check for trailing angle brackets + if let TyKind::Path(_, Path { segments, .. }) = &t.kind { + if let Some(segment) = segments.last() { + if let Some(guar) = + this.check_trailing_angle_brackets(segment, &[exp!(CloseParen)]) + { + return Ok(( + dummy_arg(segment.ident, guar), + Trailing::No, + UsePreAttrPos::No, + )); + } + } + } + + if this.token != token::Comma + && this.token != token::CloseDelim(Delimiter::Parenthesis) + { + // This wasn't actually a type, but a pattern looking like a type, + // so we are going to rollback and re-parse for recovery. + ty = this.unexpected_any(); + } } match ty { Ok(ty) => { @@ -2977,6 +2994,7 @@ impl<'a> Parser<'a> { } // If this is a C-variadic argument and we hit an error, return the error. Err(err) if this.token == token::DotDotDot => return Err(err), + Err(err) if this.unmatched_angle_bracket_count > 0 => return Err(err), // Recover from attempting to parse the argument as a type without pattern. Err(err) => { err.cancel(); diff --git a/tests/ui/fn/bad-turbofish-hints-issue-121901.rs b/tests/ui/fn/bad-turbofish-hints-issue-121901.rs new file mode 100644 index 00000000000..1425e576d75 --- /dev/null +++ b/tests/ui/fn/bad-turbofish-hints-issue-121901.rs @@ -0,0 +1,8 @@ +// Regression test for the parser wrongfully suggesting turbofish syntax in below syntax errors + +type One = for<'a> fn(Box`, found `)` +type Two = for<'a> fn(Box>); +//~^ ERROR: unmatched angle bracket + +fn main() {} diff --git a/tests/ui/fn/bad-turbofish-hints-issue-121901.stderr b/tests/ui/fn/bad-turbofish-hints-issue-121901.stderr new file mode 100644 index 00000000000..aacb326f8a2 --- /dev/null +++ b/tests/ui/fn/bad-turbofish-hints-issue-121901.stderr @@ -0,0 +1,25 @@ +error: expected one of `+`, `,`, or `>`, found `)` + --> $DIR/bad-turbofish-hints-issue-121901.rs:3:40 + | +LL | type One = for<'a> fn(Box` + | +help: you might have meant to end the type parameters here + | +LL | type One = for<'a> fn(Box); + | + + +error: unmatched angle bracket + --> $DIR/bad-turbofish-hints-issue-121901.rs:5:41 + | +LL | type Two = for<'a> fn(Box>); + | ^ + | +help: remove extra angle bracket + | +LL - type Two = for<'a> fn(Box>); +LL + type Two = for<'a> fn(Box); + | + +error: aborting due to 2 previous errors + -- cgit 1.4.1-3-g733a5 From ddcb370bc6c2de76c2a2f8b6cfb46a7b6ec553e6 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 20 Dec 2024 10:15:05 +1100 Subject: Tighten up assignment operator representations. In the AST, currently we use `BinOpKind` within `ExprKind::AssignOp` and `AssocOp::AssignOp`, even though this allows some nonsensical combinations. E.g. there is no `&&=` operator. Likewise for HIR and THIR. This commit introduces `AssignOpKind` which only includes the ten assignable operators, and uses it in `ExprKind::AssignOp` and `AssocOp::AssignOp`. (And does similar things for `hir::ExprKind` and `thir::ExprKind`.) This avoids the possibility of nonsensical combinations, as seen by the removal of the `bug!` case in `lang_item_for_binop`. The commit is mostly plumbing, including: - Adds an `impl From for BinOpKind` (AST) and `impl From for BinOp` (MIR/THIR). - `BinOpCategory` can now be created from both `BinOpKind` and `AssignOpKind`. - Replaces the `IsAssign` type with `Op`, which has more information and a few methods. - `suggest_swapping_lhs_and_rhs`: moves the condition to the call site, it's easier that way. - `check_expr_inner`: had to factor out some code into a separate method. I'm on the fence about whether avoiding the nonsensical combinations is worth the extra code. --- compiler/rustc_ast/src/ast.rs | 71 +++++- compiler/rustc_ast/src/util/parser.rs | 24 +- compiler/rustc_ast_lowering/src/expr.rs | 6 +- compiler/rustc_ast_pretty/src/pprust/state/expr.rs | 3 +- compiler/rustc_hir/src/hir.rs | 8 +- compiler/rustc_hir_pretty/src/lib.rs | 3 +- compiler/rustc_hir_typeck/src/expr.rs | 2 +- .../rustc_hir_typeck/src/fn_ctxt/suggestions.rs | 38 ++- compiler/rustc_hir_typeck/src/op.rs | 279 +++++++++++---------- compiler/rustc_hir_typeck/src/writeback.rs | 36 +-- compiler/rustc_middle/src/mir/syntax.rs | 36 +++ compiler/rustc_middle/src/thir.rs | 4 +- compiler/rustc_mir_build/src/builder/expr/stmt.rs | 10 +- compiler/rustc_mir_build/src/thir/cx/expr.rs | 19 +- compiler/rustc_parse/src/parser/expr.rs | 14 +- .../clippy/clippy_lints/src/format_push_string.rs | 4 +- .../clippy_lints/src/implicit_saturating_add.rs | 4 +- .../clippy_lints/src/implicit_saturating_sub.rs | 4 +- src/tools/clippy/clippy_lints/src/loops/utils.rs | 6 +- .../src/mixed_read_write_in_expression.rs | 5 +- .../src/operators/arithmetic_side_effects.rs | 5 +- src/tools/clippy/clippy_lints/src/operators/mod.rs | 7 +- .../clippy_lints/src/suspicious_trait_impl.rs | 28 ++- src/tools/clippy/clippy_lints/src/swap.rs | 4 +- src/tools/clippy/clippy_utils/src/sugg.rs | 2 +- src/tools/rustfmt/src/expr.rs | 2 +- 26 files changed, 388 insertions(+), 236 deletions(-) (limited to 'compiler/rustc_parse/src/parser') diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 33c20602dfd..97e6879c33e 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -981,6 +981,75 @@ impl BinOpKind { pub type BinOp = Spanned; +// Sometimes `BinOpKind` and `AssignOpKind` need the same treatment. The +// operations covered by `AssignOpKind` are a subset of those covered by +// `BinOpKind`, so it makes sense to convert `AssignOpKind` to `BinOpKind`. +impl From for BinOpKind { + fn from(op: AssignOpKind) -> BinOpKind { + match op { + AssignOpKind::AddAssign => BinOpKind::Add, + AssignOpKind::SubAssign => BinOpKind::Sub, + AssignOpKind::MulAssign => BinOpKind::Mul, + AssignOpKind::DivAssign => BinOpKind::Div, + AssignOpKind::RemAssign => BinOpKind::Rem, + AssignOpKind::BitXorAssign => BinOpKind::BitXor, + AssignOpKind::BitAndAssign => BinOpKind::BitAnd, + AssignOpKind::BitOrAssign => BinOpKind::BitOr, + AssignOpKind::ShlAssign => BinOpKind::Shl, + AssignOpKind::ShrAssign => BinOpKind::Shr, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Encodable, Decodable, HashStable_Generic)] +pub enum AssignOpKind { + /// The `+=` operator (addition) + AddAssign, + /// The `-=` operator (subtraction) + SubAssign, + /// The `*=` operator (multiplication) + MulAssign, + /// The `/=` operator (division) + DivAssign, + /// The `%=` operator (modulus) + RemAssign, + /// The `^=` operator (bitwise xor) + BitXorAssign, + /// The `&=` operator (bitwise and) + BitAndAssign, + /// The `|=` operator (bitwise or) + BitOrAssign, + /// The `<<=` operator (shift left) + ShlAssign, + /// The `>>=` operator (shift right) + ShrAssign, +} + +impl AssignOpKind { + pub fn as_str(&self) -> &'static str { + use AssignOpKind::*; + match self { + AddAssign => "+=", + SubAssign => "-=", + MulAssign => "*=", + DivAssign => "/=", + RemAssign => "%=", + BitXorAssign => "^=", + BitAndAssign => "&=", + BitOrAssign => "|=", + ShlAssign => "<<=", + ShrAssign => ">>=", + } + } + + /// AssignOps are always by value. + pub fn is_by_value(self) -> bool { + true + } +} + +pub type AssignOp = Spanned; + /// Unary operator. /// /// Note that `&data` is not an operator, it's an `AddrOf` expression. @@ -1593,7 +1662,7 @@ pub enum ExprKind { /// An assignment with an operator. /// /// E.g., `a += 1`. - AssignOp(BinOp, P, P), + AssignOp(AssignOp, P, P), /// Access of a named (e.g., `obj.foo`) or unnamed (e.g., `obj.0`) struct field. Field(P, Ident), /// An indexing operation (e.g., `foo[2]`). diff --git a/compiler/rustc_ast/src/util/parser.rs b/compiler/rustc_ast/src/util/parser.rs index 98b1fc52ed7..1e5f414fae1 100644 --- a/compiler/rustc_ast/src/util/parser.rs +++ b/compiler/rustc_ast/src/util/parser.rs @@ -1,6 +1,6 @@ use rustc_span::kw; -use crate::ast::{self, BinOpKind, RangeLimits}; +use crate::ast::{self, AssignOpKind, BinOpKind, RangeLimits}; use crate::token::{self, Token}; /// Associative operator. @@ -9,7 +9,7 @@ pub enum AssocOp { /// A binary op. Binary(BinOpKind), /// `?=` where ? is one of the assignable BinOps - AssignOp(BinOpKind), + AssignOp(AssignOpKind), /// `=` Assign, /// `as` @@ -44,16 +44,16 @@ impl AssocOp { token::Or => Some(Binary(BinOpKind::BitOr)), token::Shl => Some(Binary(BinOpKind::Shl)), token::Shr => Some(Binary(BinOpKind::Shr)), - token::PlusEq => Some(AssignOp(BinOpKind::Add)), - token::MinusEq => Some(AssignOp(BinOpKind::Sub)), - token::StarEq => Some(AssignOp(BinOpKind::Mul)), - token::SlashEq => Some(AssignOp(BinOpKind::Div)), - token::PercentEq => Some(AssignOp(BinOpKind::Rem)), - token::CaretEq => Some(AssignOp(BinOpKind::BitXor)), - token::AndEq => Some(AssignOp(BinOpKind::BitAnd)), - token::OrEq => Some(AssignOp(BinOpKind::BitOr)), - token::ShlEq => Some(AssignOp(BinOpKind::Shl)), - token::ShrEq => Some(AssignOp(BinOpKind::Shr)), + token::PlusEq => Some(AssignOp(AssignOpKind::AddAssign)), + token::MinusEq => Some(AssignOp(AssignOpKind::SubAssign)), + token::StarEq => Some(AssignOp(AssignOpKind::MulAssign)), + token::SlashEq => Some(AssignOp(AssignOpKind::DivAssign)), + token::PercentEq => Some(AssignOp(AssignOpKind::RemAssign)), + token::CaretEq => Some(AssignOp(AssignOpKind::BitXorAssign)), + token::AndEq => Some(AssignOp(AssignOpKind::BitAndAssign)), + token::OrEq => Some(AssignOp(AssignOpKind::BitOrAssign)), + token::ShlEq => Some(AssignOp(AssignOpKind::ShlAssign)), + token::ShrEq => Some(AssignOp(AssignOpKind::ShrAssign)), token::Lt => Some(Binary(BinOpKind::Lt)), token::Le => Some(Binary(BinOpKind::Le)), token::Ge => Some(Binary(BinOpKind::Ge)), diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 52291fdfb30..80bb1e8fc41 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -274,7 +274,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } ExprKind::Assign(el, er, span) => self.lower_expr_assign(el, er, *span, e.span), ExprKind::AssignOp(op, el, er) => hir::ExprKind::AssignOp( - self.lower_binop(*op), + self.lower_assign_op(*op), self.lower_expr(el), self.lower_expr(er), ), @@ -443,6 +443,10 @@ impl<'hir> LoweringContext<'_, 'hir> { Spanned { node: b.node, span: self.lower_span(b.span) } } + fn lower_assign_op(&mut self, a: AssignOp) -> AssignOp { + Spanned { node: a.node, span: self.lower_span(a.span) } + } + fn lower_legacy_const_generics( &mut self, mut f: Expr, diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index 4c301a5c6fb..df848a26d39 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -605,8 +605,7 @@ impl<'a> State<'a> { fixup.leftmost_subexpression(), ); self.space(); - self.word(op.node.as_str()); - self.word_space("="); + self.word_space(op.node.as_str()); self.print_expr_cond_paren( rhs, rhs.precedence() < ExprPrecedence::Assign, diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index f8af1b81ca9..1a6c15b66a4 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -10,9 +10,9 @@ use rustc_ast::{ LitKind, TraitObjectSyntax, UintTy, UnsafeBinderCastKind, }; pub use rustc_ast::{ - AttrId, AttrStyle, BinOp, BinOpKind, BindingMode, BorrowKind, BoundConstness, BoundPolarity, - ByRef, CaptureBy, DelimArgs, ImplPolarity, IsAuto, MetaItemInner, MetaItemLit, Movability, - Mutability, UnOp, + AssignOp, AssignOpKind, AttrId, AttrStyle, BinOp, BinOpKind, BindingMode, BorrowKind, + BoundConstness, BoundPolarity, ByRef, CaptureBy, DelimArgs, ImplPolarity, IsAuto, + MetaItemInner, MetaItemLit, Movability, Mutability, UnOp, }; use rustc_attr_data_structures::AttributeKind; use rustc_data_structures::fingerprint::Fingerprint; @@ -2648,7 +2648,7 @@ pub enum ExprKind<'hir> { /// An assignment with an operator. /// /// E.g., `a += 1`. - AssignOp(BinOp, &'hir Expr<'hir>, &'hir Expr<'hir>), + AssignOp(AssignOp, &'hir Expr<'hir>, &'hir Expr<'hir>), /// Access of a named (e.g., `obj.foo`) or unnamed (e.g., `obj.0`) struct or tuple field. Field(&'hir Expr<'hir>, Ident), /// An indexing operation (`foo[2]`). diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index 2b4e04c7956..8c0c17f7a7d 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -1572,8 +1572,7 @@ impl<'a> State<'a> { hir::ExprKind::AssignOp(op, lhs, rhs) => { self.print_expr_cond_paren(lhs, lhs.precedence() <= ExprPrecedence::Assign); self.space(); - self.word(op.node.as_str()); - self.word_space("="); + self.word_space(op.node.as_str()); self.print_expr_cond_paren(rhs, rhs.precedence() < ExprPrecedence::Assign); } hir::ExprKind::Field(expr, ident) => { diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index a882ea27af6..45ab8e03db5 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -512,7 +512,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.check_expr_assign(expr, expected, lhs, rhs, span) } ExprKind::AssignOp(op, lhs, rhs) => { - self.check_expr_binop_assign(expr, op, lhs, rhs, expected) + self.check_expr_assign_op(expr, op, lhs, rhs, expected) } ExprKind::Unary(unop, oprnd) => self.check_expr_unop(unop, oprnd, expected, expr), ExprKind::AddrOf(kind, mutbl, oprnd) => { diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index 3acf73d3d0e..912098c4e2d 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -3477,30 +3477,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { lhs_ty: Ty<'tcx>, rhs_expr: &'tcx hir::Expr<'tcx>, lhs_expr: &'tcx hir::Expr<'tcx>, - op: hir::BinOpKind, ) { - match op { - hir::BinOpKind::Eq => { - if let Some(partial_eq_def_id) = self.infcx.tcx.lang_items().eq_trait() - && self - .infcx - .type_implements_trait(partial_eq_def_id, [rhs_ty, lhs_ty], self.param_env) - .must_apply_modulo_regions() - { - let sm = self.tcx.sess.source_map(); - if let Ok(rhs_snippet) = sm.span_to_snippet(rhs_expr.span) - && let Ok(lhs_snippet) = sm.span_to_snippet(lhs_expr.span) - { - err.note(format!("`{rhs_ty}` implements `PartialEq<{lhs_ty}>`")); - err.multipart_suggestion( - "consider swapping the equality", - vec![(lhs_expr.span, rhs_snippet), (rhs_expr.span, lhs_snippet)], - Applicability::MaybeIncorrect, - ); - } - } + if let Some(partial_eq_def_id) = self.infcx.tcx.lang_items().eq_trait() + && self + .infcx + .type_implements_trait(partial_eq_def_id, [rhs_ty, lhs_ty], self.param_env) + .must_apply_modulo_regions() + { + let sm = self.tcx.sess.source_map(); + if let Ok(rhs_snippet) = sm.span_to_snippet(rhs_expr.span) + && let Ok(lhs_snippet) = sm.span_to_snippet(lhs_expr.span) + { + err.note(format!("`{rhs_ty}` implements `PartialEq<{lhs_ty}>`")); + err.multipart_suggestion( + "consider swapping the equality", + vec![(lhs_expr.span, rhs_snippet), (rhs_expr.span, lhs_snippet)], + Applicability::MaybeIncorrect, + ); } - _ => {} } } } diff --git a/compiler/rustc_hir_typeck/src/op.rs b/compiler/rustc_hir_typeck/src/op.rs index a90c5da20b5..93f77b8409f 100644 --- a/compiler/rustc_hir_typeck/src/op.rs +++ b/compiler/rustc_hir_typeck/src/op.rs @@ -24,22 +24,23 @@ use crate::Expectation; impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// Checks a `a = b` - pub(crate) fn check_expr_binop_assign( + pub(crate) fn check_expr_assign_op( &self, expr: &'tcx hir::Expr<'tcx>, - op: hir::BinOp, + op: hir::AssignOp, lhs: &'tcx hir::Expr<'tcx>, rhs: &'tcx hir::Expr<'tcx>, expected: Expectation<'tcx>, ) -> Ty<'tcx> { let (lhs_ty, rhs_ty, return_ty) = - self.check_overloaded_binop(expr, lhs, rhs, op, IsAssign::Yes, expected); + self.check_overloaded_binop(expr, lhs, rhs, Op::AssignOp(op), expected); + let category = BinOpCategory::from(op.node); let ty = if !lhs_ty.is_ty_var() && !rhs_ty.is_ty_var() - && is_builtin_binop(lhs_ty, rhs_ty, op.node) + && is_builtin_binop(lhs_ty, rhs_ty, category) { - self.enforce_builtin_binop_types(lhs.span, lhs_ty, rhs.span, rhs_ty, op.node); + self.enforce_builtin_binop_types(lhs.span, lhs_ty, rhs.span, rhs_ty, category); self.tcx.types.unit } else { return_ty @@ -51,7 +52,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .lookup_op_method( (lhs, lhs_deref_ty), Some((rhs, rhs_ty)), - lang_item_for_binop(self.tcx, op.node, IsAssign::Yes), + lang_item_for_binop(self.tcx, Op::AssignOp(op)), op.span, expected, ) @@ -63,7 +64,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .lookup_op_method( (lhs, lhs_ty), Some((rhs, rhs_ty)), - lang_item_for_binop(self.tcx, op.node, IsAssign::Yes), + lang_item_for_binop(self.tcx, Op::AssignOp(op)), op.span, expected, ) @@ -118,14 +119,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Otherwise, we always treat operators as if they are // overloaded. This is the way to be most flexible w/r/t // types that get inferred. - let (lhs_ty, rhs_ty, return_ty) = self.check_overloaded_binop( - expr, - lhs_expr, - rhs_expr, - op, - IsAssign::No, - expected, - ); + let (lhs_ty, rhs_ty, return_ty) = + self.check_overloaded_binop(expr, lhs_expr, rhs_expr, Op::BinOp(op), expected); // Supply type inference hints if relevant. Probably these // hints should be enforced during select as part of the @@ -139,16 +134,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // deduce that the result type should be `u32`, even // though we don't know yet what type 2 has and hence // can't pin this down to a specific impl. + let category = BinOpCategory::from(op.node); if !lhs_ty.is_ty_var() && !rhs_ty.is_ty_var() - && is_builtin_binop(lhs_ty, rhs_ty, op.node) + && is_builtin_binop(lhs_ty, rhs_ty, category) { let builtin_return_ty = self.enforce_builtin_binop_types( lhs_expr.span, lhs_ty, rhs_expr.span, rhs_ty, - op.node, + category, ); self.demand_eqtype(expr.span, builtin_return_ty, return_ty); builtin_return_ty @@ -165,16 +161,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { lhs_ty: Ty<'tcx>, rhs_span: Span, rhs_ty: Ty<'tcx>, - op: hir::BinOpKind, + category: BinOpCategory, ) -> Ty<'tcx> { - debug_assert!(is_builtin_binop(lhs_ty, rhs_ty, op)); + debug_assert!(is_builtin_binop(lhs_ty, rhs_ty, category)); // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work. // (See https://github.com/rust-lang/rust/issues/57447.) let (lhs_ty, rhs_ty) = (deref_ty_if_possible(lhs_ty), deref_ty_if_possible(rhs_ty)); let tcx = self.tcx; - match BinOpCategory::from(op) { + match category { BinOpCategory::Shortcircuit => { self.demand_suptype(lhs_span, tcx.types.bool, lhs_ty); self.demand_suptype(rhs_span, tcx.types.bool, rhs_ty); @@ -205,17 +201,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expr: &'tcx hir::Expr<'tcx>, lhs_expr: &'tcx hir::Expr<'tcx>, rhs_expr: &'tcx hir::Expr<'tcx>, - op: hir::BinOp, - is_assign: IsAssign, + op: Op, expected: Expectation<'tcx>, ) -> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>) { - debug!( - "check_overloaded_binop(expr.hir_id={}, op={:?}, is_assign={:?})", - expr.hir_id, op, is_assign - ); + debug!("check_overloaded_binop(expr.hir_id={}, op={:?})", expr.hir_id, op); - let lhs_ty = match is_assign { - IsAssign::No => { + let lhs_ty = match op { + Op::BinOp(_) => { // Find a suitable supertype of the LHS expression's type, by coercing to // a type variable, to pass as the `Self` to the trait, avoiding invariant // trait matching creating lifetime constraints that are too strict. @@ -225,7 +217,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let fresh_var = self.next_ty_var(lhs_expr.span); self.demand_coerce(lhs_expr, lhs_ty, fresh_var, Some(rhs_expr), AllowTwoPhase::No) } - IsAssign::Yes => { + Op::AssignOp(_) => { // rust-lang/rust#52126: We have to use strict // equivalence on the LHS of an assign-op like `+=`; // overwritten or mutably-borrowed places cannot be @@ -246,8 +238,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let result = self.lookup_op_method( (lhs_expr, lhs_ty), Some((rhs_expr, rhs_ty_var)), - lang_item_for_binop(self.tcx, op.node, is_assign), - op.span, + lang_item_for_binop(self.tcx, op), + op.span(), expected, ); @@ -257,15 +249,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { rhs_ty_var, Some(lhs_expr), |err, ty| { - self.suggest_swapping_lhs_and_rhs(err, ty, lhs_ty, rhs_expr, lhs_expr, op.node); + if let Op::BinOp(binop) = op + && binop.node == hir::BinOpKind::Eq + { + self.suggest_swapping_lhs_and_rhs(err, ty, lhs_ty, rhs_expr, lhs_expr); + } }, ); let rhs_ty = self.resolve_vars_with_obligations(rhs_ty); let return_ty = match result { Ok(method) => { - let by_ref_binop = !op.node.is_by_value(); - if is_assign == IsAssign::Yes || by_ref_binop { + let by_ref_binop = !op.is_by_value(); + if matches!(op, Op::AssignOp(_)) || by_ref_binop { if let ty::Ref(_, _, mutbl) = method.sig.inputs()[0].kind() { let mutbl = AutoBorrowMutability::new(*mutbl, AllowTwoPhase::Yes); let autoref = Adjustment { @@ -306,31 +302,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Ty::new_misc_error(self.tcx) } Err(errors) => { - let (_, trait_def_id) = lang_item_for_binop(self.tcx, op.node, is_assign); + let (_, trait_def_id) = lang_item_for_binop(self.tcx, op); let missing_trait = trait_def_id .map(|def_id| with_no_trimmed_paths!(self.tcx.def_path_str(def_id))); let mut path = None; let lhs_ty_str = self.tcx.short_string(lhs_ty, &mut path); let rhs_ty_str = self.tcx.short_string(rhs_ty, &mut path); - let (mut err, output_def_id) = match is_assign { - IsAssign::Yes => { + let (mut err, output_def_id) = match op { + Op::AssignOp(assign_op) => { + let s = assign_op.node.as_str(); let mut err = struct_span_code_err!( self.dcx(), expr.span, E0368, - "binary assignment operation `{}=` cannot be applied to type `{}`", - op.node.as_str(), + "binary assignment operation `{}` cannot be applied to type `{}`", + s, lhs_ty_str, ); err.span_label( lhs_expr.span, - format!("cannot use `{}=` on type `{}`", op.node.as_str(), lhs_ty_str), + format!("cannot use `{}` on type `{}`", s, lhs_ty_str), ); self.note_unmet_impls_on_type(&mut err, errors, false); (err, None) } - IsAssign::No => { - let message = match op.node { + Op::BinOp(bin_op) => { + let message = match bin_op.node { hir::BinOpKind::Add => { format!("cannot add `{rhs_ty_str}` to `{lhs_ty_str}`") } @@ -366,8 +363,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } _ => format!( "binary operation `{}` cannot be applied to type `{}`", - op.node.as_str(), - lhs_ty_str, + bin_op.node.as_str(), + lhs_ty_str ), }; let output_def_id = trait_def_id.and_then(|def_id| { @@ -380,7 +377,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .cloned() }); let mut err = - struct_span_code_err!(self.dcx(), op.span, E0369, "{message}"); + struct_span_code_err!(self.dcx(), bin_op.span, E0369, "{message}"); if !lhs_expr.span.eq(&rhs_expr.span) { err.span_label(lhs_expr.span, lhs_ty_str.clone()); err.span_label(rhs_expr.span, rhs_ty_str); @@ -413,19 +410,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .lookup_op_method( (lhs_expr, lhs_deref_ty), Some((rhs_expr, rhs_ty)), - lang_item_for_binop(self.tcx, op.node, is_assign), - op.span, + lang_item_for_binop(self.tcx, op), + op.span(), expected, ) .is_ok() { let msg = format!( - "`{}{}` can be used on `{}` if you dereference the left-hand side", - op.node.as_str(), - match is_assign { - IsAssign::Yes => "=", - IsAssign::No => "", - }, + "`{}` can be used on `{}` if you dereference the left-hand side", + op.as_str(), self.tcx.short_string(lhs_deref_ty, err.long_ty_path()), ); err.span_suggestion_verbose( @@ -447,15 +440,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .lookup_op_method( (lhs_expr, lhs_adjusted_ty), Some((rhs_expr, rhs_adjusted_ty)), - lang_item_for_binop(self.tcx, op.node, is_assign), - op.span, + lang_item_for_binop(self.tcx, op), + op.span(), expected, ) .is_ok() { let lhs = self.tcx.short_string(lhs_adjusted_ty, err.long_ty_path()); let rhs = self.tcx.short_string(rhs_adjusted_ty, err.long_ty_path()); - let op = op.node.as_str(); + let op = op.as_str(); err.note(format!("an implementation for `{lhs} {op} {rhs}` exists")); if let Some(lhs_new_mutbl) = lhs_new_mutbl @@ -505,8 +498,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.lookup_op_method( (lhs_expr, lhs_ty), Some((rhs_expr, rhs_ty)), - lang_item_for_binop(self.tcx, op.node, is_assign), - op.span, + lang_item_for_binop(self.tcx, op), + op.span(), expected, ) .is_ok() @@ -518,13 +511,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // We should suggest `a + b` => `*a + b` if `a` is copy, and suggest // `a += b` => `*a += b` if a is a mut ref. - if !op.span.can_be_used_for_suggestions() { + if !op.span().can_be_used_for_suggestions() { // Suppress suggestions when lhs and rhs are not in the same span as the error - } else if is_assign == IsAssign::Yes + } else if let Op::AssignOp(_) = op && let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) { suggest_deref_binop(&mut err, lhs_deref_ty); - } else if is_assign == IsAssign::No + } else if let Op::BinOp(_) = op && let ty::Ref(region, lhs_deref_ty, mutbl) = lhs_ty.kind() { if self.type_is_copy_modulo_regions(self.param_env, *lhs_deref_ty) { @@ -579,10 +572,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } if let Some(missing_trait) = missing_trait { - if op.node == hir::BinOpKind::Add - && self.check_str_addition( - lhs_expr, rhs_expr, lhs_ty, rhs_ty, &mut err, is_assign, op, - ) + if matches!( + op, + Op::BinOp(Spanned { node: hir::BinOpKind::Add, .. }) + | Op::AssignOp(Spanned { node: hir::AssignOpKind::AddAssign, .. }) + ) && self + .check_str_addition(lhs_expr, rhs_expr, lhs_ty, rhs_ty, &mut err, op) { // This has nothing here because it means we did string // concatenation (e.g., "Hello " + "World!"). This means @@ -599,8 +594,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .lookup_op_method( (lhs_expr, lhs_ty), Some((rhs_expr, rhs_ty)), - lang_item_for_binop(self.tcx, op.node, is_assign), - op.span, + lang_item_for_binop(self.tcx, op), + op.span(), expected, ) .unwrap_err(); @@ -650,9 +645,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Suggest using `add`, `offset` or `offset_from` for pointer - {integer}, // pointer + {integer} or pointer - pointer. - if op.span.can_be_used_for_suggestions() { - match op.node { - hir::BinOpKind::Add if lhs_ty.is_raw_ptr() && rhs_ty.is_integral() => { + if op.span().can_be_used_for_suggestions() { + match op { + Op::BinOp(Spanned { node: hir::BinOpKind::Add, .. }) + if lhs_ty.is_raw_ptr() && rhs_ty.is_integral() => + { err.multipart_suggestion( "consider using `wrapping_add` or `add` for pointer + {integer}", vec![ @@ -665,7 +662,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Applicability::MaybeIncorrect, ); } - hir::BinOpKind::Sub => { + Op::BinOp(Spanned { node: hir::BinOpKind::Sub, .. }) => { if lhs_ty.is_raw_ptr() && rhs_ty.is_integral() { err.multipart_suggestion( "consider using `wrapping_sub` or `sub` for \ @@ -721,8 +718,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { lhs_ty: Ty<'tcx>, rhs_ty: Ty<'tcx>, err: &mut Diag<'_>, - is_assign: IsAssign, - op: hir::BinOp, + op: Op, ) -> bool { let str_concat_note = "string concatenation requires an owned `String` on the left"; let rm_borrow_msg = "remove the borrow to obtain an owned `String`"; @@ -741,8 +737,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { r_ty.kind(), ty::Ref(_, inner_ty, _) if *inner_ty.kind() == ty::Str )) => { - if let IsAssign::No = is_assign { // Do not supply this message if `&str += &str` - err.span_label(op.span, "`+` cannot be used to concatenate two `&str` strings"); + if let Op::BinOp(_) = op { // Do not supply this message if `&str += &str` + err.span_label( + op.span(), + "`+` cannot be used to concatenate two `&str` strings" + ); err.note(str_concat_note); if let hir::ExprKind::AddrOf(_, _, lhs_inner_expr) = lhs_expr.kind { err.span_suggestion_verbose( @@ -766,11 +765,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if (*l_ty.kind() == ty::Str || is_std_string(l_ty)) && is_std_string(rhs_ty) => { err.span_label( - op.span, + op.span(), "`+` cannot be used to concatenate a `&str` with a `String`", ); - match is_assign { - IsAssign::No => { + match op { + Op::BinOp(_) => { let sugg_msg; let lhs_sugg = if let hir::ExprKind::AddrOf(_, _, lhs_inner_expr) = lhs_expr.kind { sugg_msg = "remove the borrow on the left and add one on the right"; @@ -789,7 +788,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Applicability::MachineApplicable, ); } - IsAssign::Yes => { + Op::AssignOp(_) => { err.note(str_concat_note); } } @@ -991,37 +990,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } -fn lang_item_for_binop( - tcx: TyCtxt<'_>, - op: hir::BinOpKind, - is_assign: IsAssign, -) -> (Symbol, Option) { +fn lang_item_for_binop(tcx: TyCtxt<'_>, op: Op) -> (Symbol, Option) { let lang = tcx.lang_items(); - if is_assign == IsAssign::Yes { - match op { - hir::BinOpKind::Add => (sym::add_assign, lang.add_assign_trait()), - hir::BinOpKind::Sub => (sym::sub_assign, lang.sub_assign_trait()), - hir::BinOpKind::Mul => (sym::mul_assign, lang.mul_assign_trait()), - hir::BinOpKind::Div => (sym::div_assign, lang.div_assign_trait()), - hir::BinOpKind::Rem => (sym::rem_assign, lang.rem_assign_trait()), - hir::BinOpKind::BitXor => (sym::bitxor_assign, lang.bitxor_assign_trait()), - hir::BinOpKind::BitAnd => (sym::bitand_assign, lang.bitand_assign_trait()), - hir::BinOpKind::BitOr => (sym::bitor_assign, lang.bitor_assign_trait()), - hir::BinOpKind::Shl => (sym::shl_assign, lang.shl_assign_trait()), - hir::BinOpKind::Shr => (sym::shr_assign, lang.shr_assign_trait()), - hir::BinOpKind::Lt - | hir::BinOpKind::Le - | hir::BinOpKind::Ge - | hir::BinOpKind::Gt - | hir::BinOpKind::Eq - | hir::BinOpKind::Ne - | hir::BinOpKind::And - | hir::BinOpKind::Or => { - bug!("impossible assignment operation: {}=", op.as_str()) - } - } - } else { - match op { + match op { + Op::AssignOp(op) => match op.node { + hir::AssignOpKind::AddAssign => (sym::add_assign, lang.add_assign_trait()), + hir::AssignOpKind::SubAssign => (sym::sub_assign, lang.sub_assign_trait()), + hir::AssignOpKind::MulAssign => (sym::mul_assign, lang.mul_assign_trait()), + hir::AssignOpKind::DivAssign => (sym::div_assign, lang.div_assign_trait()), + hir::AssignOpKind::RemAssign => (sym::rem_assign, lang.rem_assign_trait()), + hir::AssignOpKind::BitXorAssign => (sym::bitxor_assign, lang.bitxor_assign_trait()), + hir::AssignOpKind::BitAndAssign => (sym::bitand_assign, lang.bitand_assign_trait()), + hir::AssignOpKind::BitOrAssign => (sym::bitor_assign, lang.bitor_assign_trait()), + hir::AssignOpKind::ShlAssign => (sym::shl_assign, lang.shl_assign_trait()), + hir::AssignOpKind::ShrAssign => (sym::shr_assign, lang.shr_assign_trait()), + }, + Op::BinOp(op) => match op.node { hir::BinOpKind::Add => (sym::add, lang.add_trait()), hir::BinOpKind::Sub => (sym::sub, lang.sub_trait()), hir::BinOpKind::Mul => (sym::mul, lang.mul_trait()), @@ -1041,7 +1025,7 @@ fn lang_item_for_binop( hir::BinOpKind::And | hir::BinOpKind::Or => { bug!("&& and || are not overloadable") } - } + }, } } @@ -1056,6 +1040,7 @@ fn lang_item_for_unop(tcx: TyCtxt<'_>, op: hir::UnOp) -> (Symbol, Option for BinOpCategory { fn from(op: hir::BinOpKind) -> BinOpCategory { + use hir::BinOpKind::*; match op { - hir::BinOpKind::Shl | hir::BinOpKind::Shr => BinOpCategory::Shift, + Shl | Shr => BinOpCategory::Shift, + Add | Sub | Mul | Div | Rem => BinOpCategory::Math, + BitXor | BitAnd | BitOr => BinOpCategory::Bitwise, + Eq | Ne | Lt | Le | Ge | Gt => BinOpCategory::Comparison, + And | Or => BinOpCategory::Shortcircuit, + } + } +} - hir::BinOpKind::Add - | hir::BinOpKind::Sub - | hir::BinOpKind::Mul - | hir::BinOpKind::Div - | hir::BinOpKind::Rem => BinOpCategory::Math, +impl From for BinOpCategory { + fn from(op: hir::AssignOpKind) -> BinOpCategory { + use hir::AssignOpKind::*; + match op { + ShlAssign | ShrAssign => BinOpCategory::Shift, + AddAssign | SubAssign | MulAssign | DivAssign | RemAssign => BinOpCategory::Math, + BitXorAssign | BitAndAssign | BitOrAssign => BinOpCategory::Bitwise, + } + } +} - hir::BinOpKind::BitXor | hir::BinOpKind::BitAnd | hir::BinOpKind::BitOr => { - BinOpCategory::Bitwise - } +/// An assignment op (e.g. `a += b`), or a binary op (e.g. `a + b`). +#[derive(Clone, Copy, Debug, PartialEq)] +enum Op { + BinOp(hir::BinOp), + AssignOp(hir::AssignOp), +} - hir::BinOpKind::Eq - | hir::BinOpKind::Ne - | hir::BinOpKind::Lt - | hir::BinOpKind::Le - | hir::BinOpKind::Ge - | hir::BinOpKind::Gt => BinOpCategory::Comparison, +impl Op { + fn span(&self) -> Span { + match self { + Op::BinOp(op) => op.span, + Op::AssignOp(op) => op.span, + } + } - hir::BinOpKind::And | hir::BinOpKind::Or => BinOpCategory::Shortcircuit, + fn as_str(&self) -> &'static str { + match self { + Op::BinOp(op) => op.node.as_str(), + Op::AssignOp(op) => op.node.as_str(), } } -} -/// Whether the binary operation is an assignment (`a += b`), or not (`a + b`) -#[derive(Clone, Copy, Debug, PartialEq)] -enum IsAssign { - No, - Yes, + fn is_by_value(&self) -> bool { + match self { + Op::BinOp(op) => op.node.is_by_value(), + Op::AssignOp(op) => op.node.is_by_value(), + } + } } /// Dereferences a single level of immutable referencing. @@ -1135,27 +1140,24 @@ fn deref_ty_if_possible(ty: Ty<'_>) -> Ty<'_> { /// Reason #2 is the killer. I tried for a while to always use /// overloaded logic and just check the types in constants/codegen after /// the fact, and it worked fine, except for SIMD types. -nmatsakis -fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, op: hir::BinOpKind) -> bool { +fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, category: BinOpCategory) -> bool { // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work. // (See https://github.com/rust-lang/rust/issues/57447.) let (lhs, rhs) = (deref_ty_if_possible(lhs), deref_ty_if_possible(rhs)); - match BinOpCategory::from(op) { + match category.into() { BinOpCategory::Shortcircuit => true, - BinOpCategory::Shift => { lhs.references_error() || rhs.references_error() || lhs.is_integral() && rhs.is_integral() } - BinOpCategory::Math => { lhs.references_error() || rhs.references_error() || lhs.is_integral() && rhs.is_integral() || lhs.is_floating_point() && rhs.is_floating_point() } - BinOpCategory::Bitwise => { lhs.references_error() || rhs.references_error() @@ -1163,7 +1165,6 @@ fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, op: hir::BinOpKind) -> b || lhs.is_floating_point() && rhs.is_floating_point() || lhs.is_bool() && rhs.is_bool() } - BinOpCategory::Comparison => { lhs.references_error() || rhs.references_error() || lhs.is_scalar() && rhs.is_scalar() } diff --git a/compiler/rustc_hir_typeck/src/writeback.rs b/compiler/rustc_hir_typeck/src/writeback.rs index d457133c703..06f82b361b0 100644 --- a/compiler/rustc_hir_typeck/src/writeback.rs +++ b/compiler/rustc_hir_typeck/src/writeback.rs @@ -160,7 +160,7 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { self.typeck_results.node_args_mut().remove(e.hir_id); } } - hir::ExprKind::Binary(ref op, lhs, rhs) | hir::ExprKind::AssignOp(ref op, lhs, rhs) => { + hir::ExprKind::Binary(ref op, lhs, rhs) => { let lhs_ty = self.typeck_results.node_type(lhs.hir_id); let rhs_ty = self.typeck_results.node_type(rhs.hir_id); @@ -168,25 +168,27 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { self.typeck_results.type_dependent_defs_mut().remove(e.hir_id); self.typeck_results.node_args_mut().remove(e.hir_id); - match e.kind { - hir::ExprKind::Binary(..) => { - if !op.node.is_by_value() { - let mut adjustments = self.typeck_results.adjustments_mut(); - if let Some(a) = adjustments.get_mut(lhs.hir_id) { - a.pop(); - } - if let Some(a) = adjustments.get_mut(rhs.hir_id) { - a.pop(); - } - } + if !op.node.is_by_value() { + let mut adjustments = self.typeck_results.adjustments_mut(); + if let Some(a) = adjustments.get_mut(lhs.hir_id) { + a.pop(); } - hir::ExprKind::AssignOp(..) - if let Some(a) = - self.typeck_results.adjustments_mut().get_mut(lhs.hir_id) => - { + if let Some(a) = adjustments.get_mut(rhs.hir_id) { a.pop(); } - _ => {} + } + } + } + hir::ExprKind::AssignOp(_, lhs, rhs) => { + let lhs_ty = self.typeck_results.node_type(lhs.hir_id); + let rhs_ty = self.typeck_results.node_type(rhs.hir_id); + + if lhs_ty.is_scalar() && rhs_ty.is_scalar() { + self.typeck_results.type_dependent_defs_mut().remove(e.hir_id); + self.typeck_results.node_args_mut().remove(e.hir_id); + + if let Some(a) = self.typeck_results.adjustments_mut().get_mut(lhs.hir_id) { + a.pop(); } } } diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 6d6e6a1f185..707c8d04d55 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -1668,6 +1668,42 @@ pub enum BinOp { Offset, } +// Assignment operators, e.g. `+=`. See comments on the corresponding variants +// in `BinOp` for details. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, HashStable)] +pub enum AssignOp { + AddAssign, + SubAssign, + MulAssign, + DivAssign, + RemAssign, + BitXorAssign, + BitAndAssign, + BitOrAssign, + ShlAssign, + ShrAssign, +} + +// Sometimes `BinOp` and `AssignOp` need the same treatment. The operations +// covered by `AssignOp` are a subset of those covered by `BinOp`, so it makes +// sense to convert `AssignOp` to `BinOp`. +impl From for BinOp { + fn from(op: AssignOp) -> BinOp { + match op { + AssignOp::AddAssign => BinOp::Add, + AssignOp::SubAssign => BinOp::Sub, + AssignOp::MulAssign => BinOp::Mul, + AssignOp::DivAssign => BinOp::Div, + AssignOp::RemAssign => BinOp::Rem, + AssignOp::BitXorAssign => BinOp::BitXor, + AssignOp::BitAndAssign => BinOp::BitAnd, + AssignOp::BitOrAssign => BinOp::BitOr, + AssignOp::ShlAssign => BinOp::Shl, + AssignOp::ShrAssign => BinOp::Shr, + } + } +} + // Some nodes are used a lot. Make sure they don't unintentionally get bigger. #[cfg(target_pointer_width = "64")] mod size_asserts { diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index 6783bbf8bf4..8d373cb3b30 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -27,7 +27,7 @@ use tracing::instrument; use crate::middle::region; use crate::mir::interpret::AllocId; -use crate::mir::{self, BinOp, BorrowKind, FakeReadCause, UnOp}; +use crate::mir::{self, AssignOp, BinOp, BorrowKind, FakeReadCause, UnOp}; use crate::thir::visit::for_each_immediate_subpat; use crate::ty::adjustment::PointerCoercion; use crate::ty::layout::IntegerExt; @@ -403,7 +403,7 @@ pub enum ExprKind<'tcx> { }, /// A *non-overloaded* operation assignment, e.g. `lhs += rhs`. AssignOp { - op: BinOp, + op: AssignOp, lhs: ExprId, rhs: ExprId, }, diff --git a/compiler/rustc_mir_build/src/builder/expr/stmt.rs b/compiler/rustc_mir_build/src/builder/expr/stmt.rs index 7f8a0a34c31..2dff26f02f3 100644 --- a/compiler/rustc_mir_build/src/builder/expr/stmt.rs +++ b/compiler/rustc_mir_build/src/builder/expr/stmt.rs @@ -78,8 +78,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // because AssignOp is only legal for Copy types // (overloaded ops should be desugared into a call). let result = unpack!( - block = - this.build_binary_op(block, op, expr_span, lhs_ty, Operand::Copy(lhs), rhs) + block = this.build_binary_op( + block, + op.into(), + expr_span, + lhs_ty, + Operand::Copy(lhs), + rhs + ) ); this.cfg.push_assign(block, source_info, lhs, result); diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index b8af77245f2..31e22e69111 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -9,7 +9,7 @@ use rustc_middle::hir::place::{ Place as HirPlace, PlaceBase as HirPlaceBase, ProjectionKind as HirProjectionKind, }; use rustc_middle::middle::region; -use rustc_middle::mir::{self, BinOp, BorrowKind, UnOp}; +use rustc_middle::mir::{self, AssignOp, BinOp, BorrowKind, UnOp}; use rustc_middle::thir::*; use rustc_middle::ty::adjustment::{ Adjust, Adjustment, AutoBorrow, AutoBorrowMutability, PointerCoercion, @@ -489,7 +489,7 @@ impl<'tcx> ThirBuildCx<'tcx> { self.overloaded_operator(expr, Box::new([lhs, rhs])) } else { ExprKind::AssignOp { - op: bin_op(op.node), + op: assign_op(op.node), lhs: self.mirror_expr(lhs), rhs: self.mirror_expr(rhs), } @@ -1347,3 +1347,18 @@ fn bin_op(op: hir::BinOpKind) -> BinOp { _ => bug!("no equivalent for ast binop {:?}", op), } } + +fn assign_op(op: hir::AssignOpKind) -> AssignOp { + match op { + hir::AssignOpKind::AddAssign => AssignOp::AddAssign, + hir::AssignOpKind::SubAssign => AssignOp::SubAssign, + hir::AssignOpKind::MulAssign => AssignOp::MulAssign, + hir::AssignOpKind::DivAssign => AssignOp::DivAssign, + hir::AssignOpKind::RemAssign => AssignOp::RemAssign, + hir::AssignOpKind::BitXorAssign => AssignOp::BitXorAssign, + hir::AssignOpKind::BitAndAssign => AssignOp::BitAndAssign, + hir::AssignOpKind::BitOrAssign => AssignOp::BitOrAssign, + hir::AssignOpKind::ShlAssign => AssignOp::ShlAssign, + hir::AssignOpKind::ShrAssign => AssignOp::ShrAssign, + } +} diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index e1e6b93abf3..841d967d934 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -14,10 +14,10 @@ use rustc_ast::util::classify; use rustc_ast::util::parser::{AssocOp, ExprPrecedence, Fixity, prec_let_scrutinee_needs_par}; use rustc_ast::visit::{Visitor, walk_expr}; use rustc_ast::{ - self as ast, AnonConst, Arm, AttrStyle, AttrVec, BinOp, BinOpKind, BlockCheckMode, CaptureBy, - ClosureBinder, DUMMY_NODE_ID, Expr, ExprField, ExprKind, FnDecl, FnRetTy, Label, MacCall, - MetaItemLit, Movability, Param, RangeLimits, StmtKind, Ty, TyKind, UnOp, UnsafeBinderCastKind, - YieldKind, + self as ast, AnonConst, Arm, AssignOp, AssignOpKind, AttrStyle, AttrVec, BinOp, BinOpKind, + BlockCheckMode, CaptureBy, ClosureBinder, DUMMY_NODE_ID, Expr, ExprField, ExprKind, FnDecl, + FnRetTy, Label, MacCall, MetaItemLit, Movability, Param, RangeLimits, StmtKind, Ty, TyKind, + UnOp, UnsafeBinderCastKind, YieldKind, }; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_errors::{Applicability, Diag, PResult, StashKey, Subdiagnostic}; @@ -359,7 +359,7 @@ impl<'a> Parser<'a> { ( Some( AssocOp::Binary(BinOpKind::Shr | BinOpKind::Gt | BinOpKind::Ge) - | AssocOp::AssignOp(BinOpKind::Shr), + | AssocOp::AssignOp(AssignOpKind::ShrAssign), ), _, ) if self.restrictions.contains(Restrictions::CONST_EXPR) => { @@ -3914,8 +3914,8 @@ impl<'a> Parser<'a> { self.dcx().emit_err(errors::LeftArrowOperator { span }); } - fn mk_assign_op(&self, binop: BinOp, lhs: P, rhs: P) -> ExprKind { - ExprKind::AssignOp(binop, lhs, rhs) + fn mk_assign_op(&self, assign_op: AssignOp, lhs: P, rhs: P) -> ExprKind { + ExprKind::AssignOp(assign_op, lhs, rhs) } fn mk_range( diff --git a/src/tools/clippy/clippy_lints/src/format_push_string.rs b/src/tools/clippy/clippy_lints/src/format_push_string.rs index 68cc50f3939..b64d608c0c7 100644 --- a/src/tools/clippy/clippy_lints/src/format_push_string.rs +++ b/src/tools/clippy/clippy_lints/src/format_push_string.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher; use clippy_utils::ty::is_type_lang_item; -use rustc_hir::{BinOpKind, Expr, ExprKind, LangItem, MatchSource}; +use rustc_hir::{AssignOpKind, Expr, ExprKind, LangItem, MatchSource}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::sym; @@ -77,7 +77,7 @@ impl<'tcx> LateLintPass<'tcx> for FormatPushString { return; } }, - ExprKind::AssignOp(op, left, arg) if op.node == BinOpKind::Add && is_string(cx, left) => arg, + ExprKind::AssignOp(op, left, arg) if op.node == AssignOpKind::AddAssign && is_string(cx, left) => arg, _ => return, }; if is_format(cx, arg) { diff --git a/src/tools/clippy/clippy_lints/src/implicit_saturating_add.rs b/src/tools/clippy/clippy_lints/src/implicit_saturating_add.rs index 41d2b18803d..185fc2aa2d4 100644 --- a/src/tools/clippy/clippy_lints/src/implicit_saturating_add.rs +++ b/src/tools/clippy/clippy_lints/src/implicit_saturating_add.rs @@ -5,7 +5,7 @@ use clippy_utils::source::snippet_with_context; use rustc_ast::ast::{LitIntType, LitKind}; use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Stmt, StmtKind}; +use rustc_hir::{AssignOpKind, BinOpKind, Block, Expr, ExprKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{IntTy, Ty, UintTy}; use rustc_session::declare_lint_pass; @@ -68,7 +68,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingAdd { && ex.span.ctxt() == ctxt && expr1.span.ctxt() == ctxt && clippy_utils::SpanlessEq::new(cx).eq_expr(l, target) - && BinOpKind::Add == op1.node + && AssignOpKind::AddAssign == op1.node && let ExprKind::Lit(lit) = value.kind && let LitKind::Int(Pu128(1), LitIntType::Unsuffixed) = lit.node && block.expr.is_none() diff --git a/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs b/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs index cbc3e2ccd5b..5d7d3ae3f24 100644 --- a/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs +++ b/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs @@ -8,7 +8,7 @@ use clippy_utils::{ use rustc_ast::ast::LitKind; use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; -use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, QPath}; +use rustc_hir::{AssignOpKind, BinOp, BinOpKind, Expr, ExprKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; use rustc_span::Span; @@ -366,7 +366,7 @@ fn subtracts_one<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<&'a Exp match peel_blocks_with_stmt(expr).kind { ExprKind::AssignOp(ref op1, target, value) => { // Check if literal being subtracted is one - (BinOpKind::Sub == op1.node && is_integer_literal(value, 1)).then_some(target) + (AssignOpKind::SubAssign == op1.node && is_integer_literal(value, 1)).then_some(target) }, ExprKind::Assign(target, value, _) => { if let ExprKind::Binary(ref op1, left1, right1) = value.kind diff --git a/src/tools/clippy/clippy_lints/src/loops/utils.rs b/src/tools/clippy/clippy_lints/src/loops/utils.rs index a5185d38e7c..3c6fbef2bda 100644 --- a/src/tools/clippy/clippy_lints/src/loops/utils.rs +++ b/src/tools/clippy/clippy_lints/src/loops/utils.rs @@ -3,7 +3,9 @@ use clippy_utils::{get_parent_expr, is_integer_const, path_to_local, path_to_loc use rustc_ast::ast::{LitIntType, LitKind}; use rustc_errors::Applicability; use rustc_hir::intravisit::{Visitor, walk_expr, walk_local}; -use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, LetStmt, Mutability, PatKind}; +use rustc_hir::{ + AssignOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, LetStmt, Mutability, PatKind +}; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, Ty}; @@ -58,7 +60,7 @@ impl<'tcx> Visitor<'tcx> for IncrementVisitor<'_, 'tcx> { match parent.kind { ExprKind::AssignOp(op, lhs, rhs) => { if lhs.hir_id == expr.hir_id { - *state = if op.node == BinOpKind::Add + *state = if op.node == AssignOpKind::AddAssign && is_integer_const(self.cx, rhs, 1) && *state == IncrementVisitorVarState::Initial && self.depth == 0 diff --git a/src/tools/clippy/clippy_lints/src/mixed_read_write_in_expression.rs b/src/tools/clippy/clippy_lints/src/mixed_read_write_in_expression.rs index be728e6c8b7..fbd287f5285 100644 --- a/src/tools/clippy/clippy_lints/src/mixed_read_write_in_expression.rs +++ b/src/tools/clippy/clippy_lints/src/mixed_read_write_in_expression.rs @@ -261,10 +261,11 @@ fn check_expr<'tcx>(vis: &mut ReadVisitor<'_, 'tcx>, expr: &'tcx Expr<'_>) -> St | ExprKind::Assign(..) | ExprKind::Index(..) | ExprKind::Repeat(_, _) - | ExprKind::Struct(_, _, _) => { + | ExprKind::Struct(_, _, _) + | ExprKind::AssignOp(_, _, _) => { walk_expr(vis, expr); }, - ExprKind::Binary(op, _, _) | ExprKind::AssignOp(op, _, _) => { + ExprKind::Binary(op, _, _) => { if op.node == BinOpKind::And || op.node == BinOpKind::Or { // x && y and x || y always evaluate x first, so these are // strictly sequenced. diff --git a/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs b/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs index 08fcd17e555..a78a342d4fe 100644 --- a/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs +++ b/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs @@ -335,9 +335,12 @@ impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects { return; } match &expr.kind { - hir::ExprKind::AssignOp(op, lhs, rhs) | hir::ExprKind::Binary(op, lhs, rhs) => { + hir::ExprKind::Binary(op, lhs, rhs) => { self.manage_bin_ops(cx, expr, op.node, lhs, rhs); }, + hir::ExprKind::AssignOp(op, lhs, rhs) => { + self.manage_bin_ops(cx, expr, op.node.into(), lhs, rhs); + }, hir::ExprKind::MethodCall(ps, receiver, args, _) => { self.manage_method_call(args, cx, expr, ps, receiver); }, diff --git a/src/tools/clippy/clippy_lints/src/operators/mod.rs b/src/tools/clippy/clippy_lints/src/operators/mod.rs index f758d08d366..d32c062cf56 100644 --- a/src/tools/clippy/clippy_lints/src/operators/mod.rs +++ b/src/tools/clippy/clippy_lints/src/operators/mod.rs @@ -913,9 +913,10 @@ impl<'tcx> LateLintPass<'tcx> for Operators { ); }, ExprKind::AssignOp(op, lhs, rhs) => { - self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs); - misrefactored_assign_op::check(cx, e, op.node, lhs, rhs); - modulo_arithmetic::check(cx, e, op.node, lhs, rhs, false); + let bin_op = op.node.into(); + self.arithmetic_context.check_binary(cx, e, bin_op, lhs, rhs); + misrefactored_assign_op::check(cx, e, bin_op, lhs, rhs); + modulo_arithmetic::check(cx, e, bin_op, lhs, rhs, false); }, ExprKind::Assign(lhs, rhs, _) => { assign_op_pattern::check(cx, e, lhs, rhs); diff --git a/src/tools/clippy/clippy_lints/src/suspicious_trait_impl.rs b/src/tools/clippy/clippy_lints/src/suspicious_trait_impl.rs index fb426e91bf0..56bd8fefdb4 100644 --- a/src/tools/clippy/clippy_lints/src/suspicious_trait_impl.rs +++ b/src/tools/clippy/clippy_lints/src/suspicious_trait_impl.rs @@ -5,6 +5,7 @@ use core::ops::ControlFlow; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; +use rustc_span::Span; declare_clippy_lint! { /// ### What it does @@ -56,8 +57,27 @@ declare_lint_pass!(SuspiciousImpl => [SUSPICIOUS_ARITHMETIC_IMPL, SUSPICIOUS_OP_ impl<'tcx> LateLintPass<'tcx> for SuspiciousImpl { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { - if let hir::ExprKind::Binary(binop, _, _) | hir::ExprKind::AssignOp(binop, ..) = expr.kind - && let Some((binop_trait_lang, op_assign_trait_lang)) = binop_traits(binop.node) + match expr.kind { + hir::ExprKind::Binary(op, _, _) => { + self.check_expr_inner(cx, expr, op.node, op.span); + } + hir::ExprKind::AssignOp(op, _, _) => { + self.check_expr_inner(cx, expr, op.node.into(), op.span); + } + _ => {} + } + } +} + +impl<'tcx> SuspiciousImpl { + fn check_expr_inner( + &mut self, + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + binop: hir::BinOpKind, + span: Span, + ) { + if let Some((binop_trait_lang, op_assign_trait_lang)) = binop_traits(binop) && let Some(binop_trait_id) = cx.tcx.lang_items().get(binop_trait_lang) && let Some(op_assign_trait_id) = cx.tcx.lang_items().get(op_assign_trait_lang) @@ -82,10 +102,10 @@ impl<'tcx> LateLintPass<'tcx> for SuspiciousImpl { span_lint( cx, lint, - binop.span, + span, format!( "suspicious use of `{}` in `{}` impl", - binop.node.as_str(), + binop.as_str(), cx.tcx.item_name(trait_id) ), ); diff --git a/src/tools/clippy/clippy_lints/src/swap.rs b/src/tools/clippy/clippy_lints/src/swap.rs index 7176d533b61..0337b74b4b1 100644 --- a/src/tools/clippy/clippy_lints/src/swap.rs +++ b/src/tools/clippy/clippy_lints/src/swap.rs @@ -10,7 +10,7 @@ use rustc_data_structures::fx::FxIndexSet; use rustc_hir::intravisit::{Visitor, walk_expr}; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Block, Expr, ExprKind, LetStmt, PatKind, QPath, Stmt, StmtKind}; +use rustc_hir::{AssignOpKind, Block, Expr, ExprKind, LetStmt, PatKind, QPath, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty; use rustc_session::declare_lint_pass; @@ -307,7 +307,7 @@ fn extract_sides_of_xor_assign<'a, 'hir>( if let StmtKind::Semi(expr) = stmt.kind && let ExprKind::AssignOp( Spanned { - node: BinOpKind::BitXor, + node: AssignOpKind::BitXorAssign, .. }, lhs, diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs index 065d48119a2..e92c0c79b25 100644 --- a/src/tools/clippy/clippy_utils/src/sugg.rs +++ b/src/tools/clippy/clippy_utils/src/sugg.rs @@ -357,7 +357,7 @@ fn binop_to_string(op: AssocOp, lhs: &str, rhs: &str) -> String { match op { AssocOp::Binary(op) => format!("{lhs} {} {rhs}", op.as_str()), AssocOp::Assign => format!("{lhs} = {rhs}"), - AssocOp::AssignOp(op) => format!("{lhs} {}= {rhs}", op.as_str()), + AssocOp::AssignOp(op) => format!("{lhs} {} {rhs}", op.as_str()), AssocOp::Cast => format!("{lhs} as {rhs}"), AssocOp::Range(limits) => format!("{lhs}{}{rhs}", limits.as_str()), } diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs index 65120770edd..be6b483bfff 100644 --- a/src/tools/rustfmt/src/expr.rs +++ b/src/tools/rustfmt/src/expr.rs @@ -2058,7 +2058,7 @@ fn rewrite_assignment( context: &RewriteContext<'_>, lhs: &ast::Expr, rhs: &ast::Expr, - op: Option<&ast::BinOp>, + op: Option<&ast::AssignOp>, shape: Shape, ) -> RewriteResult { let operator_str = match op { -- cgit 1.4.1-3-g733a5 From b9e13cb539c23a320e3856eba82711cd6360407e Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 4 Apr 2025 09:34:01 +1100 Subject: Apply `Recovery::Forbidden` when reparsing pasted macro fragments. Fixes #137874. Removes `tests/crashes/137874.rs`; the new test is simpler (defines its own macro) but tests the same thing. The changes to the output of `tests/ui/associated-consts/issue-93835.rs` partly undo the changes seen when `NtTy` was removed in #133436, which is good. --- compiler/rustc_parse/src/parser/mod.rs | 17 +++++++++++- tests/crashes/137874.rs | 4 --- tests/ui/associated-consts/issue-93835.rs | 3 +- tests/ui/associated-consts/issue-93835.stderr | 32 ++++++---------------- tests/ui/macros/failed-to-reparse-issue-137874.rs | 12 ++++++++ .../macros/failed-to-reparse-issue-137874.stderr | 10 +++++++ 6 files changed, 48 insertions(+), 30 deletions(-) delete mode 100644 tests/crashes/137874.rs create mode 100644 tests/ui/macros/failed-to-reparse-issue-137874.rs create mode 100644 tests/ui/macros/failed-to-reparse-issue-137874.stderr (limited to 'compiler/rustc_parse/src/parser') diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 392a1c1057a..3b0861a9942 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -512,6 +512,14 @@ impl<'a> Parser<'a> { self } + #[inline] + fn with_recovery(&mut self, recovery: Recovery, f: impl FnOnce(&mut Self) -> T) -> T { + let old = mem::replace(&mut self.recovery, recovery); + let res = f(self); + self.recovery = old; + res + } + /// Whether the parser is allowed to recover from broken code. /// /// If this returns false, recovering broken code into valid code (especially if this recovery does lookahead) @@ -770,7 +778,14 @@ impl<'a> Parser<'a> { && match_mv_kind(mv_kind) { self.bump(); - let res = f(self).expect("failed to reparse {mv_kind:?}"); + + // Recovery is disabled when parsing macro arguments, so it must + // also be disabled when reparsing pasted macro arguments, + // otherwise we get inconsistent results (e.g. #137874). + let res = self.with_recovery(Recovery::Forbidden, |this| { + f(this).expect("failed to reparse {mv_kind:?}") + }); + if let token::CloseDelim(delim) = self.token.kind && let Delimiter::Invisible(InvisibleOrigin::MetaVar(mv_kind)) = delim && match_mv_kind(mv_kind) diff --git a/tests/crashes/137874.rs b/tests/crashes/137874.rs deleted file mode 100644 index 44718809024..00000000000 --- a/tests/crashes/137874.rs +++ /dev/null @@ -1,4 +0,0 @@ -//@ known-bug: #137874 -fn a() { - match b { deref !(0c) }; -} diff --git a/tests/ui/associated-consts/issue-93835.rs b/tests/ui/associated-consts/issue-93835.rs index 048681f0477..d6c2acaa9ed 100644 --- a/tests/ui/associated-consts/issue-93835.rs +++ b/tests/ui/associated-consts/issue-93835.rs @@ -3,11 +3,10 @@ fn e() { type_ascribe!(p, a>); //~^ ERROR cannot find type `a` in this scope - //~| ERROR path separator must be a double colon //~| ERROR cannot find value //~| ERROR associated const equality + //~| ERROR cannot find trait `p` in this scope //~| ERROR associated const equality - //~| ERROR failed to resolve: use of unresolved module or unlinked crate `p` } fn main() {} diff --git a/tests/ui/associated-consts/issue-93835.stderr b/tests/ui/associated-consts/issue-93835.stderr index e154ae25de2..551b50d0eb6 100644 --- a/tests/ui/associated-consts/issue-93835.stderr +++ b/tests/ui/associated-consts/issue-93835.stderr @@ -1,15 +1,3 @@ -error: path separator must be a double colon - --> $DIR/issue-93835.rs:4:25 - | -LL | type_ascribe!(p, a>); - | ^ - | - = note: if you meant to annotate an expression with a type, the type ascription syntax has been removed, see issue #101728 -help: use a double colon instead - | -LL | type_ascribe!(p, a>); - | + - error[E0425]: cannot find value `p` in this scope --> $DIR/issue-93835.rs:4:19 | @@ -22,6 +10,12 @@ error[E0412]: cannot find type `a` in this scope LL | type_ascribe!(p, a>); | ^ not found in this scope +error[E0405]: cannot find trait `p` in this scope + --> $DIR/issue-93835.rs:4:26 + | +LL | type_ascribe!(p, a>); + | ^ not found in this scope + error[E0658]: associated const equality is incomplete --> $DIR/issue-93835.rs:4:28 | @@ -43,15 +37,7 @@ LL | type_ascribe!(p, a>); = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -error[E0433]: failed to resolve: use of unresolved module or unlinked crate `p` - --> $DIR/issue-93835.rs:4:24 - | -LL | type_ascribe!(p, a>); - | ^ use of unresolved module or unlinked crate `p` - | - = help: you might be missing a crate named `p` - -error: aborting due to 6 previous errors +error: aborting due to 5 previous errors -Some errors have detailed explanations: E0412, E0425, E0433, E0658. -For more information about an error, try `rustc --explain E0412`. +Some errors have detailed explanations: E0405, E0412, E0425, E0658. +For more information about an error, try `rustc --explain E0405`. diff --git a/tests/ui/macros/failed-to-reparse-issue-137874.rs b/tests/ui/macros/failed-to-reparse-issue-137874.rs new file mode 100644 index 00000000000..3e55ac376fe --- /dev/null +++ b/tests/ui/macros/failed-to-reparse-issue-137874.rs @@ -0,0 +1,12 @@ +// This originally crashed because `Recovery::Forbidden` wasn't being applied +// when fragments pasted by declarative macros were reparsed. + +macro_rules! m { + ($p:pat) => { + if let $p = 0 {} + } +} + +fn main() { + m!(0X0); //~ ERROR invalid base prefix for number literal +} diff --git a/tests/ui/macros/failed-to-reparse-issue-137874.stderr b/tests/ui/macros/failed-to-reparse-issue-137874.stderr new file mode 100644 index 00000000000..5bbb8b7f9e2 --- /dev/null +++ b/tests/ui/macros/failed-to-reparse-issue-137874.stderr @@ -0,0 +1,10 @@ +error: invalid base prefix for number literal + --> $DIR/failed-to-reparse-issue-137874.rs:11:8 + | +LL | m!(0X0); + | ^^^ help: try making the prefix lowercase (notice the capitalization): `0x0` + | + = note: base prefixes (`0xff`, `0b1010`, `0o755`) are lowercase + +error: aborting due to 1 previous error + -- cgit 1.4.1-3-g733a5 From aff2bc7a8815b89dd8dccf2564f5178118c1a3ec Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 4 Apr 2025 14:44:45 +0200 Subject: Replace `rustc_lexer/unescape` with `rustc-literal-escaper` crate --- Cargo.lock | 10 +- compiler/rustc_ast/Cargo.toml | 2 +- compiler/rustc_ast/src/util/literal.rs | 2 +- compiler/rustc_lexer/src/lib.rs | 1 - compiler/rustc_lexer/src/unescape.rs | 438 --------------------- compiler/rustc_lexer/src/unescape/tests.rs | 286 -------------- compiler/rustc_parse/Cargo.toml | 1 + compiler/rustc_parse/src/lexer/mod.rs | 10 +- .../src/lexer/unescape_error_reporting.rs | 2 +- compiler/rustc_parse/src/parser/expr.rs | 2 +- compiler/rustc_parse_format/Cargo.toml | 1 + compiler/rustc_parse_format/src/lib.rs | 10 +- 12 files changed, 22 insertions(+), 743 deletions(-) delete mode 100644 compiler/rustc_lexer/src/unescape.rs delete mode 100644 compiler/rustc_lexer/src/unescape/tests.rs (limited to 'compiler/rustc_parse/src/parser') diff --git a/Cargo.lock b/Cargo.lock index 96526f7e9e7..0bdddcc7a64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3150,6 +3150,12 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc-literal-escaper" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdd0fcb1409d38cb2d940400497e2384a4a04b8685ee92a0a7a8986ccd72115" + [[package]] name = "rustc-main" version = "0.0.0" @@ -3242,10 +3248,10 @@ version = "0.0.0" dependencies = [ "bitflags", "memchr", + "rustc-literal-escaper", "rustc_ast_ir", "rustc_data_structures", "rustc_index", - "rustc_lexer", "rustc_macros", "rustc_serialize", "rustc_span", @@ -4200,6 +4206,7 @@ name = "rustc_parse" version = "0.0.0" dependencies = [ "bitflags", + "rustc-literal-escaper", "rustc_ast", "rustc_ast_pretty", "rustc_data_structures", @@ -4222,6 +4229,7 @@ dependencies = [ name = "rustc_parse_format" version = "0.0.0" dependencies = [ + "rustc-literal-escaper", "rustc_index", "rustc_lexer", ] diff --git a/compiler/rustc_ast/Cargo.toml b/compiler/rustc_ast/Cargo.toml index 902287d0328..73b57327204 100644 --- a/compiler/rustc_ast/Cargo.toml +++ b/compiler/rustc_ast/Cargo.toml @@ -10,7 +10,7 @@ memchr = "2.7.4" rustc_ast_ir = { path = "../rustc_ast_ir" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_index = { path = "../rustc_index" } -rustc_lexer = { path = "../rustc_lexer" } +rustc-literal-escaper = "0.0.1" rustc_macros = { path = "../rustc_macros" } rustc_serialize = { path = "../rustc_serialize" } rustc_span = { path = "../rustc_span" } diff --git a/compiler/rustc_ast/src/util/literal.rs b/compiler/rustc_ast/src/util/literal.rs index 6896ac723fa..b8526cf9d95 100644 --- a/compiler/rustc_ast/src/util/literal.rs +++ b/compiler/rustc_ast/src/util/literal.rs @@ -2,7 +2,7 @@ use std::{ascii, fmt, str}; -use rustc_lexer::unescape::{ +use rustc_literal_escaper::{ MixedUnit, Mode, byte_from_char, unescape_byte, unescape_char, unescape_mixed, unescape_unicode, }; use rustc_span::{Span, Symbol, kw, sym}; diff --git a/compiler/rustc_lexer/src/lib.rs b/compiler/rustc_lexer/src/lib.rs index 61638e45253..f9c71b2fa65 100644 --- a/compiler/rustc_lexer/src/lib.rs +++ b/compiler/rustc_lexer/src/lib.rs @@ -26,7 +26,6 @@ // tidy-alphabetical-end mod cursor; -pub mod unescape; #[cfg(test)] mod tests; diff --git a/compiler/rustc_lexer/src/unescape.rs b/compiler/rustc_lexer/src/unescape.rs deleted file mode 100644 index d6ea4249247..00000000000 --- a/compiler/rustc_lexer/src/unescape.rs +++ /dev/null @@ -1,438 +0,0 @@ -//! Utilities for validating string and char literals and turning them into -//! values they represent. - -use std::ops::Range; -use std::str::Chars; - -use Mode::*; - -#[cfg(test)] -mod tests; - -/// Errors and warnings that can occur during string unescaping. They mostly -/// relate to malformed escape sequences, but there are a few that are about -/// other problems. -#[derive(Debug, PartialEq, Eq)] -pub enum EscapeError { - /// Expected 1 char, but 0 were found. - ZeroChars, - /// Expected 1 char, but more than 1 were found. - MoreThanOneChar, - - /// Escaped '\' character without continuation. - LoneSlash, - /// Invalid escape character (e.g. '\z'). - InvalidEscape, - /// Raw '\r' encountered. - BareCarriageReturn, - /// Raw '\r' encountered in raw string. - BareCarriageReturnInRawString, - /// Unescaped character that was expected to be escaped (e.g. raw '\t'). - EscapeOnlyChar, - - /// Numeric character escape is too short (e.g. '\x1'). - TooShortHexEscape, - /// Invalid character in numeric escape (e.g. '\xz') - InvalidCharInHexEscape, - /// Character code in numeric escape is non-ascii (e.g. '\xFF'). - OutOfRangeHexEscape, - - /// '\u' not followed by '{'. - NoBraceInUnicodeEscape, - /// Non-hexadecimal value in '\u{..}'. - InvalidCharInUnicodeEscape, - /// '\u{}' - EmptyUnicodeEscape, - /// No closing brace in '\u{..}', e.g. '\u{12'. - UnclosedUnicodeEscape, - /// '\u{_12}' - LeadingUnderscoreUnicodeEscape, - /// More than 6 characters in '\u{..}', e.g. '\u{10FFFF_FF}' - OverlongUnicodeEscape, - /// Invalid in-bound unicode character code, e.g. '\u{DFFF}'. - LoneSurrogateUnicodeEscape, - /// Out of bounds unicode character code, e.g. '\u{FFFFFF}'. - OutOfRangeUnicodeEscape, - - /// Unicode escape code in byte literal. - UnicodeEscapeInByte, - /// Non-ascii character in byte literal, byte string literal, or raw byte string literal. - NonAsciiCharInByte, - - // `\0` in a C string literal. - NulInCStr, - - /// After a line ending with '\', the next line contains whitespace - /// characters that are not skipped. - UnskippedWhitespaceWarning, - - /// After a line ending with '\', multiple lines are skipped. - MultipleSkippedLinesWarning, -} - -impl EscapeError { - /// Returns true for actual errors, as opposed to warnings. - pub fn is_fatal(&self) -> bool { - !matches!( - self, - EscapeError::UnskippedWhitespaceWarning | EscapeError::MultipleSkippedLinesWarning - ) - } -} - -/// Takes the contents of a unicode-only (non-mixed-utf8) literal (without -/// quotes) and produces a sequence of escaped characters or errors. -/// -/// Values are returned by invoking `callback`. For `Char` and `Byte` modes, -/// the callback will be called exactly once. -pub fn unescape_unicode(src: &str, mode: Mode, callback: &mut F) -where - F: FnMut(Range, Result), -{ - match mode { - Char | Byte => { - let mut chars = src.chars(); - let res = unescape_char_or_byte(&mut chars, mode); - callback(0..(src.len() - chars.as_str().len()), res); - } - Str | ByteStr => unescape_non_raw_common(src, mode, callback), - RawStr | RawByteStr => check_raw_common(src, mode, callback), - RawCStr => check_raw_common(src, mode, &mut |r, mut result| { - if let Ok('\0') = result { - result = Err(EscapeError::NulInCStr); - } - callback(r, result) - }), - CStr => unreachable!(), - } -} - -/// Used for mixed utf8 string literals, i.e. those that allow both unicode -/// chars and high bytes. -pub enum MixedUnit { - /// Used for ASCII chars (written directly or via `\x00`..`\x7f` escapes) - /// and Unicode chars (written directly or via `\u` escapes). - /// - /// For example, if '¥' appears in a string it is represented here as - /// `MixedUnit::Char('¥')`, and it will be appended to the relevant byte - /// string as the two-byte UTF-8 sequence `[0xc2, 0xa5]` - Char(char), - - /// Used for high bytes (`\x80`..`\xff`). - /// - /// For example, if `\xa5` appears in a string it is represented here as - /// `MixedUnit::HighByte(0xa5)`, and it will be appended to the relevant - /// byte string as the single byte `0xa5`. - HighByte(u8), -} - -impl From for MixedUnit { - fn from(c: char) -> Self { - MixedUnit::Char(c) - } -} - -impl From for MixedUnit { - fn from(n: u8) -> Self { - if n.is_ascii() { MixedUnit::Char(n as char) } else { MixedUnit::HighByte(n) } - } -} - -/// Takes the contents of a mixed-utf8 literal (without quotes) and produces -/// a sequence of escaped characters or errors. -/// -/// Values are returned by invoking `callback`. -pub fn unescape_mixed(src: &str, mode: Mode, callback: &mut F) -where - F: FnMut(Range, Result), -{ - match mode { - CStr => unescape_non_raw_common(src, mode, &mut |r, mut result| { - if let Ok(MixedUnit::Char('\0')) = result { - result = Err(EscapeError::NulInCStr); - } - callback(r, result) - }), - Char | Byte | Str | RawStr | ByteStr | RawByteStr | RawCStr => unreachable!(), - } -} - -/// Takes a contents of a char literal (without quotes), and returns an -/// unescaped char or an error. -pub fn unescape_char(src: &str) -> Result { - unescape_char_or_byte(&mut src.chars(), Char) -} - -/// Takes a contents of a byte literal (without quotes), and returns an -/// unescaped byte or an error. -pub fn unescape_byte(src: &str) -> Result { - unescape_char_or_byte(&mut src.chars(), Byte).map(byte_from_char) -} - -/// What kind of literal do we parse. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Mode { - Char, - - Byte, - - Str, - RawStr, - - ByteStr, - RawByteStr, - - CStr, - RawCStr, -} - -impl Mode { - pub fn in_double_quotes(self) -> bool { - match self { - Str | RawStr | ByteStr | RawByteStr | CStr | RawCStr => true, - Char | Byte => false, - } - } - - /// Are `\x80`..`\xff` allowed? - fn allow_high_bytes(self) -> bool { - match self { - Char | Str => false, - Byte | ByteStr | CStr => true, - RawStr | RawByteStr | RawCStr => unreachable!(), - } - } - - /// Are unicode (non-ASCII) chars allowed? - #[inline] - fn allow_unicode_chars(self) -> bool { - match self { - Byte | ByteStr | RawByteStr => false, - Char | Str | RawStr | CStr | RawCStr => true, - } - } - - /// Are unicode escapes (`\u`) allowed? - fn allow_unicode_escapes(self) -> bool { - match self { - Byte | ByteStr => false, - Char | Str | CStr => true, - RawByteStr | RawStr | RawCStr => unreachable!(), - } - } - - pub fn prefix_noraw(self) -> &'static str { - match self { - Char | Str | RawStr => "", - Byte | ByteStr | RawByteStr => "b", - CStr | RawCStr => "c", - } - } -} - -fn scan_escape + From>( - chars: &mut Chars<'_>, - mode: Mode, -) -> Result { - // Previous character was '\\', unescape what follows. - let res: char = match chars.next().ok_or(EscapeError::LoneSlash)? { - '"' => '"', - 'n' => '\n', - 'r' => '\r', - 't' => '\t', - '\\' => '\\', - '\'' => '\'', - '0' => '\0', - 'x' => { - // Parse hexadecimal character code. - - let hi = chars.next().ok_or(EscapeError::TooShortHexEscape)?; - let hi = hi.to_digit(16).ok_or(EscapeError::InvalidCharInHexEscape)?; - - let lo = chars.next().ok_or(EscapeError::TooShortHexEscape)?; - let lo = lo.to_digit(16).ok_or(EscapeError::InvalidCharInHexEscape)?; - - let value = (hi * 16 + lo) as u8; - - return if !mode.allow_high_bytes() && !value.is_ascii() { - Err(EscapeError::OutOfRangeHexEscape) - } else { - // This may be a high byte, but that will only happen if `T` is - // `MixedUnit`, because of the `allow_high_bytes` check above. - Ok(T::from(value)) - }; - } - 'u' => return scan_unicode(chars, mode.allow_unicode_escapes()).map(T::from), - _ => return Err(EscapeError::InvalidEscape), - }; - Ok(T::from(res)) -} - -fn scan_unicode(chars: &mut Chars<'_>, allow_unicode_escapes: bool) -> Result { - // We've parsed '\u', now we have to parse '{..}'. - - if chars.next() != Some('{') { - return Err(EscapeError::NoBraceInUnicodeEscape); - } - - // First character must be a hexadecimal digit. - let mut n_digits = 1; - let mut value: u32 = match chars.next().ok_or(EscapeError::UnclosedUnicodeEscape)? { - '_' => return Err(EscapeError::LeadingUnderscoreUnicodeEscape), - '}' => return Err(EscapeError::EmptyUnicodeEscape), - c => c.to_digit(16).ok_or(EscapeError::InvalidCharInUnicodeEscape)?, - }; - - // First character is valid, now parse the rest of the number - // and closing brace. - loop { - match chars.next() { - None => return Err(EscapeError::UnclosedUnicodeEscape), - Some('_') => continue, - Some('}') => { - if n_digits > 6 { - return Err(EscapeError::OverlongUnicodeEscape); - } - - // Incorrect syntax has higher priority for error reporting - // than unallowed value for a literal. - if !allow_unicode_escapes { - return Err(EscapeError::UnicodeEscapeInByte); - } - - break std::char::from_u32(value).ok_or({ - if value > 0x10FFFF { - EscapeError::OutOfRangeUnicodeEscape - } else { - EscapeError::LoneSurrogateUnicodeEscape - } - }); - } - Some(c) => { - let digit: u32 = c.to_digit(16).ok_or(EscapeError::InvalidCharInUnicodeEscape)?; - n_digits += 1; - if n_digits > 6 { - // Stop updating value since we're sure that it's incorrect already. - continue; - } - value = value * 16 + digit; - } - }; - } -} - -#[inline] -fn ascii_check(c: char, allow_unicode_chars: bool) -> Result { - if allow_unicode_chars || c.is_ascii() { Ok(c) } else { Err(EscapeError::NonAsciiCharInByte) } -} - -fn unescape_char_or_byte(chars: &mut Chars<'_>, mode: Mode) -> Result { - let c = chars.next().ok_or(EscapeError::ZeroChars)?; - let res = match c { - '\\' => scan_escape(chars, mode), - '\n' | '\t' | '\'' => Err(EscapeError::EscapeOnlyChar), - '\r' => Err(EscapeError::BareCarriageReturn), - _ => ascii_check(c, mode.allow_unicode_chars()), - }?; - if chars.next().is_some() { - return Err(EscapeError::MoreThanOneChar); - } - Ok(res) -} - -/// Takes a contents of a string literal (without quotes) and produces a -/// sequence of escaped characters or errors. -fn unescape_non_raw_common + From>(src: &str, mode: Mode, callback: &mut F) -where - F: FnMut(Range, Result), -{ - let mut chars = src.chars(); - let allow_unicode_chars = mode.allow_unicode_chars(); // get this outside the loop - - // The `start` and `end` computation here is complicated because - // `skip_ascii_whitespace` makes us to skip over chars without counting - // them in the range computation. - while let Some(c) = chars.next() { - let start = src.len() - chars.as_str().len() - c.len_utf8(); - let res = match c { - '\\' => { - match chars.clone().next() { - Some('\n') => { - // Rust language specification requires us to skip whitespaces - // if unescaped '\' character is followed by '\n'. - // For details see [Rust language reference] - // (https://doc.rust-lang.org/reference/tokens.html#string-literals). - skip_ascii_whitespace(&mut chars, start, &mut |range, err| { - callback(range, Err(err)) - }); - continue; - } - _ => scan_escape::(&mut chars, mode), - } - } - '"' => Err(EscapeError::EscapeOnlyChar), - '\r' => Err(EscapeError::BareCarriageReturn), - _ => ascii_check(c, allow_unicode_chars).map(T::from), - }; - let end = src.len() - chars.as_str().len(); - callback(start..end, res); - } -} - -fn skip_ascii_whitespace(chars: &mut Chars<'_>, start: usize, callback: &mut F) -where - F: FnMut(Range, EscapeError), -{ - let tail = chars.as_str(); - let first_non_space = tail - .bytes() - .position(|b| b != b' ' && b != b'\t' && b != b'\n' && b != b'\r') - .unwrap_or(tail.len()); - if tail[1..first_non_space].contains('\n') { - // The +1 accounts for the escaping slash. - let end = start + first_non_space + 1; - callback(start..end, EscapeError::MultipleSkippedLinesWarning); - } - let tail = &tail[first_non_space..]; - if let Some(c) = tail.chars().next() { - if c.is_whitespace() { - // For error reporting, we would like the span to contain the character that was not - // skipped. The +1 is necessary to account for the leading \ that started the escape. - let end = start + first_non_space + c.len_utf8() + 1; - callback(start..end, EscapeError::UnskippedWhitespaceWarning); - } - } - *chars = tail.chars(); -} - -/// Takes a contents of a string literal (without quotes) and produces a -/// sequence of characters or errors. -/// NOTE: Raw strings do not perform any explicit character escaping, here we -/// only produce errors on bare CR. -fn check_raw_common(src: &str, mode: Mode, callback: &mut F) -where - F: FnMut(Range, Result), -{ - let mut chars = src.chars(); - let allow_unicode_chars = mode.allow_unicode_chars(); // get this outside the loop - - // The `start` and `end` computation here matches the one in - // `unescape_non_raw_common` for consistency, even though this function - // doesn't have to worry about skipping any chars. - while let Some(c) = chars.next() { - let start = src.len() - chars.as_str().len() - c.len_utf8(); - let res = match c { - '\r' => Err(EscapeError::BareCarriageReturnInRawString), - _ => ascii_check(c, allow_unicode_chars), - }; - let end = src.len() - chars.as_str().len(); - callback(start..end, res); - } -} - -#[inline] -pub fn byte_from_char(c: char) -> u8 { - let res = c as u32; - debug_assert!(res <= u8::MAX as u32, "guaranteed because of ByteStr"); - res as u8 -} diff --git a/compiler/rustc_lexer/src/unescape/tests.rs b/compiler/rustc_lexer/src/unescape/tests.rs deleted file mode 100644 index 5b99495f475..00000000000 --- a/compiler/rustc_lexer/src/unescape/tests.rs +++ /dev/null @@ -1,286 +0,0 @@ -use super::*; - -#[test] -fn test_unescape_char_bad() { - fn check(literal_text: &str, expected_error: EscapeError) { - assert_eq!(unescape_char(literal_text), Err(expected_error)); - } - - check("", EscapeError::ZeroChars); - check(r"\", EscapeError::LoneSlash); - - check("\n", EscapeError::EscapeOnlyChar); - check("\t", EscapeError::EscapeOnlyChar); - check("'", EscapeError::EscapeOnlyChar); - check("\r", EscapeError::BareCarriageReturn); - - check("spam", EscapeError::MoreThanOneChar); - check(r"\x0ff", EscapeError::MoreThanOneChar); - check(r#"\"a"#, EscapeError::MoreThanOneChar); - check(r"\na", EscapeError::MoreThanOneChar); - check(r"\ra", EscapeError::MoreThanOneChar); - check(r"\ta", EscapeError::MoreThanOneChar); - check(r"\\a", EscapeError::MoreThanOneChar); - check(r"\'a", EscapeError::MoreThanOneChar); - check(r"\0a", EscapeError::MoreThanOneChar); - check(r"\u{0}x", EscapeError::MoreThanOneChar); - check(r"\u{1F63b}}", EscapeError::MoreThanOneChar); - - check(r"\v", EscapeError::InvalidEscape); - check(r"\💩", EscapeError::InvalidEscape); - check(r"\●", EscapeError::InvalidEscape); - check("\\\r", EscapeError::InvalidEscape); - - check(r"\x", EscapeError::TooShortHexEscape); - check(r"\x0", EscapeError::TooShortHexEscape); - check(r"\xf", EscapeError::TooShortHexEscape); - check(r"\xa", EscapeError::TooShortHexEscape); - check(r"\xx", EscapeError::InvalidCharInHexEscape); - check(r"\xы", EscapeError::InvalidCharInHexEscape); - check(r"\x🦀", EscapeError::InvalidCharInHexEscape); - check(r"\xtt", EscapeError::InvalidCharInHexEscape); - check(r"\xff", EscapeError::OutOfRangeHexEscape); - check(r"\xFF", EscapeError::OutOfRangeHexEscape); - check(r"\x80", EscapeError::OutOfRangeHexEscape); - - check(r"\u", EscapeError::NoBraceInUnicodeEscape); - check(r"\u[0123]", EscapeError::NoBraceInUnicodeEscape); - check(r"\u{0x}", EscapeError::InvalidCharInUnicodeEscape); - check(r"\u{", EscapeError::UnclosedUnicodeEscape); - check(r"\u{0000", EscapeError::UnclosedUnicodeEscape); - check(r"\u{}", EscapeError::EmptyUnicodeEscape); - check(r"\u{_0000}", EscapeError::LeadingUnderscoreUnicodeEscape); - check(r"\u{0000000}", EscapeError::OverlongUnicodeEscape); - check(r"\u{FFFFFF}", EscapeError::OutOfRangeUnicodeEscape); - check(r"\u{ffffff}", EscapeError::OutOfRangeUnicodeEscape); - check(r"\u{ffffff}", EscapeError::OutOfRangeUnicodeEscape); - - check(r"\u{DC00}", EscapeError::LoneSurrogateUnicodeEscape); - check(r"\u{DDDD}", EscapeError::LoneSurrogateUnicodeEscape); - check(r"\u{DFFF}", EscapeError::LoneSurrogateUnicodeEscape); - - check(r"\u{D800}", EscapeError::LoneSurrogateUnicodeEscape); - check(r"\u{DAAA}", EscapeError::LoneSurrogateUnicodeEscape); - check(r"\u{DBFF}", EscapeError::LoneSurrogateUnicodeEscape); -} - -#[test] -fn test_unescape_char_good() { - fn check(literal_text: &str, expected_char: char) { - assert_eq!(unescape_char(literal_text), Ok(expected_char)); - } - - check("a", 'a'); - check("ы", 'ы'); - check("🦀", '🦀'); - - check(r#"\""#, '"'); - check(r"\n", '\n'); - check(r"\r", '\r'); - check(r"\t", '\t'); - check(r"\\", '\\'); - check(r"\'", '\''); - check(r"\0", '\0'); - - check(r"\x00", '\0'); - check(r"\x5a", 'Z'); - check(r"\x5A", 'Z'); - check(r"\x7f", 127 as char); - - check(r"\u{0}", '\0'); - check(r"\u{000000}", '\0'); - check(r"\u{41}", 'A'); - check(r"\u{0041}", 'A'); - check(r"\u{00_41}", 'A'); - check(r"\u{4__1__}", 'A'); - check(r"\u{1F63b}", '😻'); -} - -#[test] -fn test_unescape_str_warn() { - fn check(literal: &str, expected: &[(Range, Result)]) { - let mut unescaped = Vec::with_capacity(literal.len()); - unescape_unicode(literal, Mode::Str, &mut |range, res| unescaped.push((range, res))); - assert_eq!(unescaped, expected); - } - - // Check we can handle escaped newlines at the end of a file. - check("\\\n", &[]); - check("\\\n ", &[]); - - check( - "\\\n \u{a0} x", - &[ - (0..5, Err(EscapeError::UnskippedWhitespaceWarning)), - (3..5, Ok('\u{a0}')), - (5..6, Ok(' ')), - (6..7, Ok('x')), - ], - ); - check("\\\n \n x", &[(0..7, Err(EscapeError::MultipleSkippedLinesWarning)), (7..8, Ok('x'))]); -} - -#[test] -fn test_unescape_str_good() { - fn check(literal_text: &str, expected: &str) { - let mut buf = Ok(String::with_capacity(literal_text.len())); - unescape_unicode(literal_text, Mode::Str, &mut |range, c| { - if let Ok(b) = &mut buf { - match c { - Ok(c) => b.push(c), - Err(e) => buf = Err((range, e)), - } - } - }); - assert_eq!(buf.as_deref(), Ok(expected)) - } - - check("foo", "foo"); - check("", ""); - check(" \t\n", " \t\n"); - - check("hello \\\n world", "hello world"); - check("thread's", "thread's") -} - -#[test] -fn test_unescape_byte_bad() { - fn check(literal_text: &str, expected_error: EscapeError) { - assert_eq!(unescape_byte(literal_text), Err(expected_error)); - } - - check("", EscapeError::ZeroChars); - check(r"\", EscapeError::LoneSlash); - - check("\n", EscapeError::EscapeOnlyChar); - check("\t", EscapeError::EscapeOnlyChar); - check("'", EscapeError::EscapeOnlyChar); - check("\r", EscapeError::BareCarriageReturn); - - check("spam", EscapeError::MoreThanOneChar); - check(r"\x0ff", EscapeError::MoreThanOneChar); - check(r#"\"a"#, EscapeError::MoreThanOneChar); - check(r"\na", EscapeError::MoreThanOneChar); - check(r"\ra", EscapeError::MoreThanOneChar); - check(r"\ta", EscapeError::MoreThanOneChar); - check(r"\\a", EscapeError::MoreThanOneChar); - check(r"\'a", EscapeError::MoreThanOneChar); - check(r"\0a", EscapeError::MoreThanOneChar); - - check(r"\v", EscapeError::InvalidEscape); - check(r"\💩", EscapeError::InvalidEscape); - check(r"\●", EscapeError::InvalidEscape); - - check(r"\x", EscapeError::TooShortHexEscape); - check(r"\x0", EscapeError::TooShortHexEscape); - check(r"\xa", EscapeError::TooShortHexEscape); - check(r"\xf", EscapeError::TooShortHexEscape); - check(r"\xx", EscapeError::InvalidCharInHexEscape); - check(r"\xы", EscapeError::InvalidCharInHexEscape); - check(r"\x🦀", EscapeError::InvalidCharInHexEscape); - check(r"\xtt", EscapeError::InvalidCharInHexEscape); - - check(r"\u", EscapeError::NoBraceInUnicodeEscape); - check(r"\u[0123]", EscapeError::NoBraceInUnicodeEscape); - check(r"\u{0x}", EscapeError::InvalidCharInUnicodeEscape); - check(r"\u{", EscapeError::UnclosedUnicodeEscape); - check(r"\u{0000", EscapeError::UnclosedUnicodeEscape); - check(r"\u{}", EscapeError::EmptyUnicodeEscape); - check(r"\u{_0000}", EscapeError::LeadingUnderscoreUnicodeEscape); - check(r"\u{0000000}", EscapeError::OverlongUnicodeEscape); - - check("ы", EscapeError::NonAsciiCharInByte); - check("🦀", EscapeError::NonAsciiCharInByte); - - check(r"\u{0}", EscapeError::UnicodeEscapeInByte); - check(r"\u{000000}", EscapeError::UnicodeEscapeInByte); - check(r"\u{41}", EscapeError::UnicodeEscapeInByte); - check(r"\u{0041}", EscapeError::UnicodeEscapeInByte); - check(r"\u{00_41}", EscapeError::UnicodeEscapeInByte); - check(r"\u{4__1__}", EscapeError::UnicodeEscapeInByte); - check(r"\u{1F63b}", EscapeError::UnicodeEscapeInByte); - check(r"\u{0}x", EscapeError::UnicodeEscapeInByte); - check(r"\u{1F63b}}", EscapeError::UnicodeEscapeInByte); - check(r"\u{FFFFFF}", EscapeError::UnicodeEscapeInByte); - check(r"\u{ffffff}", EscapeError::UnicodeEscapeInByte); - check(r"\u{ffffff}", EscapeError::UnicodeEscapeInByte); - check(r"\u{DC00}", EscapeError::UnicodeEscapeInByte); - check(r"\u{DDDD}", EscapeError::UnicodeEscapeInByte); - check(r"\u{DFFF}", EscapeError::UnicodeEscapeInByte); - check(r"\u{D800}", EscapeError::UnicodeEscapeInByte); - check(r"\u{DAAA}", EscapeError::UnicodeEscapeInByte); - check(r"\u{DBFF}", EscapeError::UnicodeEscapeInByte); -} - -#[test] -fn test_unescape_byte_good() { - fn check(literal_text: &str, expected_byte: u8) { - assert_eq!(unescape_byte(literal_text), Ok(expected_byte)); - } - - check("a", b'a'); - - check(r#"\""#, b'"'); - check(r"\n", b'\n'); - check(r"\r", b'\r'); - check(r"\t", b'\t'); - check(r"\\", b'\\'); - check(r"\'", b'\''); - check(r"\0", b'\0'); - - check(r"\x00", b'\0'); - check(r"\x5a", b'Z'); - check(r"\x5A", b'Z'); - check(r"\x7f", 127); - check(r"\x80", 128); - check(r"\xff", 255); - check(r"\xFF", 255); -} - -#[test] -fn test_unescape_byte_str_good() { - fn check(literal_text: &str, expected: &[u8]) { - let mut buf = Ok(Vec::with_capacity(literal_text.len())); - unescape_unicode(literal_text, Mode::ByteStr, &mut |range, c| { - if let Ok(b) = &mut buf { - match c { - Ok(c) => b.push(byte_from_char(c)), - Err(e) => buf = Err((range, e)), - } - } - }); - assert_eq!(buf.as_deref(), Ok(expected)) - } - - check("foo", b"foo"); - check("", b""); - check(" \t\n", b" \t\n"); - - check("hello \\\n world", b"hello world"); - check("thread's", b"thread's") -} - -#[test] -fn test_unescape_raw_str() { - fn check(literal: &str, expected: &[(Range, Result)]) { - let mut unescaped = Vec::with_capacity(literal.len()); - unescape_unicode(literal, Mode::RawStr, &mut |range, res| unescaped.push((range, res))); - assert_eq!(unescaped, expected); - } - - check("\r", &[(0..1, Err(EscapeError::BareCarriageReturnInRawString))]); - check("\rx", &[(0..1, Err(EscapeError::BareCarriageReturnInRawString)), (1..2, Ok('x'))]); -} - -#[test] -fn test_unescape_raw_byte_str() { - fn check(literal: &str, expected: &[(Range, Result)]) { - let mut unescaped = Vec::with_capacity(literal.len()); - unescape_unicode(literal, Mode::RawByteStr, &mut |range, res| unescaped.push((range, res))); - assert_eq!(unescaped, expected); - } - - check("\r", &[(0..1, Err(EscapeError::BareCarriageReturnInRawString))]); - check("🦀", &[(0..4, Err(EscapeError::NonAsciiCharInByte))]); - check("🦀a", &[(0..4, Err(EscapeError::NonAsciiCharInByte)), (4..5, Ok('a'))]); -} diff --git a/compiler/rustc_parse/Cargo.toml b/compiler/rustc_parse/Cargo.toml index c9dcab0c871..dec1e09d8dd 100644 --- a/compiler/rustc_parse/Cargo.toml +++ b/compiler/rustc_parse/Cargo.toml @@ -15,6 +15,7 @@ rustc_fluent_macro = { path = "../rustc_fluent_macro" } rustc_index = { path = "../rustc_index" } rustc_lexer = { path = "../rustc_lexer" } rustc_macros = { path = "../rustc_macros" } +rustc-literal-escaper = "0.0.1" rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } thin-vec = "0.2.12" diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index 1d17290e1c7..4935fc03256 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -6,8 +6,8 @@ use rustc_ast::tokenstream::TokenStream; use rustc_ast::util::unicode::contains_text_flow_control_chars; use rustc_errors::codes::*; use rustc_errors::{Applicability, Diag, DiagCtxtHandle, StashKey}; -use rustc_lexer::unescape::{self, EscapeError, Mode}; use rustc_lexer::{Base, Cursor, DocStyle, LiteralKind, RawStrError}; +use rustc_literal_escaper::{EscapeError, Mode, unescape_mixed, unescape_unicode}; use rustc_session::lint::BuiltinLintDiag; use rustc_session::lint::builtin::{ RUST_2021_PREFIXES_INCOMPATIBLE_SYNTAX, RUST_2024_GUARDED_STRING_INCOMPATIBLE_SYNTAX, @@ -970,9 +970,7 @@ impl<'psess, 'src> Lexer<'psess, 'src> { postfix_len: u32, ) -> (token::LitKind, Symbol) { self.cook_common(kind, mode, start, end, prefix_len, postfix_len, |src, mode, callback| { - unescape::unescape_unicode(src, mode, &mut |span, result| { - callback(span, result.map(drop)) - }) + unescape_unicode(src, mode, &mut |span, result| callback(span, result.map(drop))) }) } @@ -986,9 +984,7 @@ impl<'psess, 'src> Lexer<'psess, 'src> { postfix_len: u32, ) -> (token::LitKind, Symbol) { self.cook_common(kind, mode, start, end, prefix_len, postfix_len, |src, mode, callback| { - unescape::unescape_mixed(src, mode, &mut |span, result| { - callback(span, result.map(drop)) - }) + unescape_mixed(src, mode, &mut |span, result| callback(span, result.map(drop))) }) } } diff --git a/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs b/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs index 2e066f0179c..ec59a1a0131 100644 --- a/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs +++ b/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs @@ -4,7 +4,7 @@ use std::iter::once; use std::ops::Range; use rustc_errors::{Applicability, DiagCtxtHandle, ErrorGuaranteed}; -use rustc_lexer::unescape::{EscapeError, Mode}; +use rustc_literal_escaper::{EscapeError, Mode}; use rustc_span::{BytePos, Span}; use tracing::debug; diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 841d967d934..9c457f150a3 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -21,7 +21,7 @@ use rustc_ast::{ }; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_errors::{Applicability, Diag, PResult, StashKey, Subdiagnostic}; -use rustc_lexer::unescape::unescape_char; +use rustc_literal_escaper::unescape_char; use rustc_macros::Subdiagnostic; use rustc_session::errors::{ExprParenthesesNeeded, report_lit_error}; use rustc_session::lint::BuiltinLintDiag; diff --git a/compiler/rustc_parse_format/Cargo.toml b/compiler/rustc_parse_format/Cargo.toml index 289e062fb5e..b0b70cefb59 100644 --- a/compiler/rustc_parse_format/Cargo.toml +++ b/compiler/rustc_parse_format/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] # tidy-alphabetical-start rustc_lexer = { path = "../rustc_lexer" } +rustc-literal-escaper = "0.0.1" # tidy-alphabetical-end [target.'cfg(target_pointer_width = "64")'.dev-dependencies] diff --git a/compiler/rustc_parse_format/src/lib.rs b/compiler/rustc_parse_format/src/lib.rs index 97931742985..c59e6cb5c33 100644 --- a/compiler/rustc_parse_format/src/lib.rs +++ b/compiler/rustc_parse_format/src/lib.rs @@ -18,7 +18,7 @@ pub use Alignment::*; pub use Count::*; pub use Position::*; -use rustc_lexer::unescape; +use rustc_literal_escaper::{Mode, unescape_unicode}; // Note: copied from rustc_span /// Range inside of a `Span` used for diagnostics when we only have access to relative positions. @@ -1094,11 +1094,9 @@ fn find_width_map_from_snippet( fn unescape_string(string: &str) -> Option { let mut buf = String::new(); let mut ok = true; - unescape::unescape_unicode(string, unescape::Mode::Str, &mut |_, unescaped_char| { - match unescaped_char { - Ok(c) => buf.push(c), - Err(_) => ok = false, - } + unescape_unicode(string, Mode::Str, &mut |_, unescaped_char| match unescaped_char { + Ok(c) => buf.push(c), + Err(_) => ok = false, }); ok.then_some(buf) -- cgit 1.4.1-3-g733a5