diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 56b20e0ad8938..14205f66491c5 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -39,9 +39,7 @@ pub use crate::format::*; use crate::ptr::P; use crate::token::{self, CommentKind, Delimiter}; use crate::tokenstream::{DelimSpan, LazyAttrTokenStream, TokenStream}; -use crate::util::parser::{ - AssocOp, PREC_CLOSURE, PREC_JUMP, PREC_PREFIX, PREC_RANGE, PREC_UNAMBIGUOUS, -}; +use crate::util::parser::{AssocOp, ExprPrecedence}; /// A "Label" is an identifier of some point in sources, /// e.g. in the following code: @@ -1317,29 +1315,29 @@ impl Expr { Some(P(Ty { kind, id: self.id, span: self.span, tokens: None })) } - pub fn precedence(&self) -> i8 { + pub fn precedence(&self) -> ExprPrecedence { match self.kind { - ExprKind::Closure(..) => PREC_CLOSURE, + ExprKind::Closure(..) => ExprPrecedence::Closure, ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) | ExprKind::Yield(..) | ExprKind::Yeet(..) - | ExprKind::Become(..) => PREC_JUMP, + | ExprKind::Become(..) => ExprPrecedence::Jump, // `Range` claims to have higher precedence than `Assign`, but `x .. x = x` fails to // parse, instead of parsing as `(x .. x) = x`. Giving `Range` a lower precedence // ensures that `pprust` will add parentheses in the right places to get the desired // parse. - ExprKind::Range(..) => PREC_RANGE, + ExprKind::Range(..) => ExprPrecedence::Range, // Binop-like expr kinds, handled by `AssocOp`. - ExprKind::Binary(op, ..) => AssocOp::from_ast_binop(op.node).precedence() as i8, - ExprKind::Cast(..) => AssocOp::As.precedence() as i8, + ExprKind::Binary(op, ..) => AssocOp::from_ast_binop(op.node).precedence(), + ExprKind::Cast(..) => ExprPrecedence::Cast, ExprKind::Assign(..) | - ExprKind::AssignOp(..) => AssocOp::Assign.precedence() as i8, + ExprKind::AssignOp(..) => ExprPrecedence::Assign, // Unary, prefix ExprKind::AddrOf(..) @@ -1348,7 +1346,7 @@ impl Expr { // need parens sometimes. E.g. we can print `(let _ = a) && b` as `let _ = a && b` // but we need to print `(let _ = a) < b` as-is with parens. | ExprKind::Let(..) - | ExprKind::Unary(..) => PREC_PREFIX, + | ExprKind::Unary(..) => ExprPrecedence::Prefix, // Never need parens ExprKind::Array(_) @@ -1381,7 +1379,7 @@ impl Expr { | ExprKind::Underscore | ExprKind::While(..) | ExprKind::Err(_) - | ExprKind::Dummy => PREC_UNAMBIGUOUS, + | ExprKind::Dummy => ExprPrecedence::Unambiguous, } } diff --git a/compiler/rustc_ast/src/util/parser.rs b/compiler/rustc_ast/src/util/parser.rs index ed9265d51598a..e88bf27021aff 100644 --- a/compiler/rustc_ast/src/util/parser.rs +++ b/compiler/rustc_ast/src/util/parser.rs @@ -128,21 +128,21 @@ impl AssocOp { } /// Gets the precedence of this operator - pub fn precedence(&self) -> usize { + pub fn precedence(&self) -> ExprPrecedence { use AssocOp::*; match *self { - As => 14, - Multiply | Divide | Modulus => 13, - Add | Subtract => 12, - ShiftLeft | ShiftRight => 11, - BitAnd => 10, - BitXor => 9, - BitOr => 8, - Less | Greater | LessEqual | GreaterEqual | Equal | NotEqual => 7, - LAnd => 6, - LOr => 5, - DotDot | DotDotEq => 4, - Assign | AssignOp(_) => 2, + As => ExprPrecedence::Cast, + Multiply | Divide | Modulus => ExprPrecedence::Product, + Add | Subtract => ExprPrecedence::Sum, + ShiftLeft | ShiftRight => ExprPrecedence::Shift, + BitAnd => ExprPrecedence::BitAnd, + BitXor => ExprPrecedence::BitXor, + BitOr => ExprPrecedence::BitOr, + Less | Greater | LessEqual | GreaterEqual | Equal | NotEqual => ExprPrecedence::Compare, + LAnd => ExprPrecedence::LAnd, + LOr => ExprPrecedence::LOr, + DotDot | DotDotEq => ExprPrecedence::Range, + Assign | AssignOp(_) => ExprPrecedence::Assign, } } @@ -229,17 +229,44 @@ impl AssocOp { } } -pub const PREC_CLOSURE: i8 = -40; -pub const PREC_JUMP: i8 = -30; -pub const PREC_RANGE: i8 = -10; -// The range 2..=14 is reserved for AssocOp binary operator precedences. -pub const PREC_PREFIX: i8 = 50; -pub const PREC_UNAMBIGUOUS: i8 = 60; -pub const PREC_FORCE_PAREN: i8 = 100; +#[derive(Clone, Copy, PartialEq, PartialOrd)] +pub enum ExprPrecedence { + Closure, + // return, break, yield + Jump, + // = += -= *= /= %= &= |= ^= <<= >>= + Assign, + // .. ..= + Range, + // || + LOr, + // && + LAnd, + // == != < > <= >= + Compare, + // | + BitOr, + // ^ + BitXor, + // & + BitAnd, + // << >> + Shift, + // + - + Sum, + // * / % + Product, + // as + Cast, + // unary - * ! & &mut + Prefix, + // paths, loops, function calls, array indexing, field expressions, method calls + Unambiguous, +} /// In `let p = e`, operators with precedence `<=` this one requires parentheses in `e`. -pub fn prec_let_scrutinee_needs_par() -> usize { - AssocOp::LAnd.precedence() +pub fn prec_let_scrutinee_needs_par() -> ExprPrecedence { + ExprPrecedence::LAnd } /// Suppose we have `let _ = e` and the `order` of `e`. @@ -247,8 +274,8 @@ pub fn prec_let_scrutinee_needs_par() -> usize { /// /// Conversely, suppose that we have `(let _ = a) OP b` and `order` is that of `OP`. /// Can we print this as `let _ = a OP b`? -pub fn needs_par_as_let_scrutinee(order: i8) -> bool { - order <= prec_let_scrutinee_needs_par() as i8 +pub fn needs_par_as_let_scrutinee(order: ExprPrecedence) -> bool { + order <= prec_let_scrutinee_needs_par() } /// Expressions that syntactically contain an "exterior" struct literal i.e., not surrounded by any diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index e9c49e9f682f9..c239cb249c3c2 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -5,7 +5,7 @@ use itertools::{Itertools, Position}; use rustc_ast::ptr::P; use rustc_ast::util::classify; use rustc_ast::util::literal::escape_byte_str_symbol; -use rustc_ast::util::parser::{self, AssocOp, Fixity}; +use rustc_ast::util::parser::{self, AssocOp, ExprPrecedence, Fixity}; use rustc_ast::{ self as ast, BlockCheckMode, FormatAlignment, FormatArgPosition, FormatArgsPiece, FormatCount, FormatDebugHex, FormatSign, FormatTrait, token, @@ -212,9 +212,9 @@ impl<'a> State<'a> { } fn print_expr_call(&mut self, func: &ast::Expr, args: &[P], fixup: FixupContext) { - let prec = match func.kind { - ast::ExprKind::Field(..) => parser::PREC_FORCE_PAREN, - _ => parser::PREC_UNAMBIGUOUS, + let needs_paren = match func.kind { + ast::ExprKind::Field(..) => true, + _ => func.precedence() < ExprPrecedence::Unambiguous, }; // Independent of parenthesization related to precedence, we must @@ -233,7 +233,7 @@ impl<'a> State<'a> { // because the latter is valid syntax but with the incorrect meaning. // It's a match-expression followed by tuple-expression, not a function // call. - self.print_expr_cond_paren(func, func.precedence() < prec, fixup.leftmost_subexpression()); + self.print_expr_cond_paren(func, needs_paren, fixup.leftmost_subexpression()); self.print_call_post(args) } @@ -256,7 +256,7 @@ impl<'a> State<'a> { // a statement containing an expression. self.print_expr_cond_paren( receiver, - receiver.precedence() < parser::PREC_UNAMBIGUOUS, + receiver.precedence() < ExprPrecedence::Unambiguous, fixup, ); @@ -276,21 +276,22 @@ impl<'a> State<'a> { fixup: FixupContext, ) { let assoc_op = AssocOp::from_ast_binop(op.node); - let prec = assoc_op.precedence() as i8; - let fixity = assoc_op.fixity(); - - let (left_prec, right_prec) = match fixity { - Fixity::Left => (prec, prec + 1), - Fixity::Right => (prec + 1, prec), - Fixity::None => (prec + 1, prec + 1), + let binop_prec = assoc_op.precedence(); + let left_prec = lhs.precedence(); + let right_prec = rhs.precedence(); + + let (mut left_needs_paren, right_needs_paren) = match assoc_op.fixity() { + Fixity::Left => (left_prec < binop_prec, right_prec <= binop_prec), + Fixity::Right => (left_prec <= binop_prec, right_prec < binop_prec), + Fixity::None => (left_prec <= binop_prec, right_prec <= binop_prec), }; - let left_prec = match (&lhs.kind, op.node) { + match (&lhs.kind, op.node) { // These cases need parens: `x as i32 < y` has the parser thinking that `i32 < y` is // the beginning of a path type. It starts trying to parse `x as (i32 < y ...` instead // of `(x as i32) < ...`. We need to convince it _not_ to do that. (&ast::ExprKind::Cast { .. }, ast::BinOpKind::Lt | ast::BinOpKind::Shl) => { - parser::PREC_FORCE_PAREN + left_needs_paren = true; } // We are given `(let _ = a) OP b`. // @@ -300,33 +301,23 @@ impl<'a> State<'a> { // - Otherwise, e.g. when we have `(let a = b) < c` in AST, // parens are required since the parser would interpret `let a = b < c` as // `let a = (b < c)`. To achieve this, we force parens. - (&ast::ExprKind::Let { .. }, _) if !parser::needs_par_as_let_scrutinee(prec) => { - parser::PREC_FORCE_PAREN + (&ast::ExprKind::Let { .. }, _) if !parser::needs_par_as_let_scrutinee(binop_prec) => { + left_needs_paren = true; } - _ => left_prec, - }; - - self.print_expr_cond_paren( - lhs, - lhs.precedence() < left_prec, - fixup.leftmost_subexpression(), - ); + _ => {} + } + self.print_expr_cond_paren(lhs, left_needs_paren, fixup.leftmost_subexpression()); self.space(); self.word_space(op.node.as_str()); - - self.print_expr_cond_paren( - rhs, - rhs.precedence() < right_prec, - fixup.subsequent_subexpression(), - ); + self.print_expr_cond_paren(rhs, right_needs_paren, fixup.subsequent_subexpression()); } fn print_expr_unary(&mut self, op: ast::UnOp, expr: &ast::Expr, fixup: FixupContext) { self.word(op.as_str()); self.print_expr_cond_paren( expr, - expr.precedence() < parser::PREC_PREFIX, + expr.precedence() < ExprPrecedence::Prefix, fixup.subsequent_subexpression(), ); } @@ -348,7 +339,7 @@ impl<'a> State<'a> { } self.print_expr_cond_paren( expr, - expr.precedence() < parser::PREC_PREFIX, + expr.precedence() < ExprPrecedence::Prefix, fixup.subsequent_subexpression(), ); } @@ -432,10 +423,9 @@ impl<'a> State<'a> { self.print_token_literal(lit, expr.span) } ast::ExprKind::Cast(expr, ty) => { - let prec = AssocOp::As.precedence() as i8; self.print_expr_cond_paren( expr, - expr.precedence() < prec, + expr.precedence() < ExprPrecedence::Cast, fixup.leftmost_subexpression(), ); self.space(); @@ -512,7 +502,7 @@ impl<'a> State<'a> { MatchKind::Postfix => { self.print_expr_cond_paren( expr, - expr.precedence() < parser::PREC_UNAMBIGUOUS, + expr.precedence() < ExprPrecedence::Unambiguous, fixup, ); self.word_nbsp(".match"); @@ -576,31 +566,31 @@ impl<'a> State<'a> { ast::ExprKind::Await(expr, _) => { self.print_expr_cond_paren( expr, - expr.precedence() < parser::PREC_UNAMBIGUOUS, + expr.precedence() < ExprPrecedence::Unambiguous, fixup, ); self.word(".await"); } ast::ExprKind::Assign(lhs, rhs, _) => { - let prec = AssocOp::Assign.precedence() as i8; self.print_expr_cond_paren( lhs, - lhs.precedence() <= prec, + // Ranges are allowed on the right-hand side of assignment, + // but not the left. `(a..b) = c` needs parentheses. + lhs.precedence() <= ExprPrecedence::Range, fixup.leftmost_subexpression(), ); self.space(); self.word_space("="); self.print_expr_cond_paren( rhs, - rhs.precedence() < prec, + rhs.precedence() < ExprPrecedence::Assign, fixup.subsequent_subexpression(), ); } ast::ExprKind::AssignOp(op, lhs, rhs) => { - let prec = AssocOp::Assign.precedence() as i8; self.print_expr_cond_paren( lhs, - lhs.precedence() <= prec, + lhs.precedence() <= ExprPrecedence::Range, fixup.leftmost_subexpression(), ); self.space(); @@ -608,14 +598,14 @@ impl<'a> State<'a> { self.word_space("="); self.print_expr_cond_paren( rhs, - rhs.precedence() < prec, + rhs.precedence() < ExprPrecedence::Assign, fixup.subsequent_subexpression(), ); } ast::ExprKind::Field(expr, ident) => { self.print_expr_cond_paren( expr, - expr.precedence() < parser::PREC_UNAMBIGUOUS, + expr.precedence() < ExprPrecedence::Unambiguous, fixup, ); self.word("."); @@ -624,7 +614,7 @@ impl<'a> State<'a> { ast::ExprKind::Index(expr, index, _) => { self.print_expr_cond_paren( expr, - expr.precedence() < parser::PREC_UNAMBIGUOUS, + expr.precedence() < ExprPrecedence::Unambiguous, fixup.leftmost_subexpression(), ); self.word("["); @@ -636,7 +626,7 @@ impl<'a> State<'a> { // than `Assign`, but `x .. x = x` gives a parse error instead of `x .. (x = x)`. // Here we use a fake precedence value so that any child with lower precedence than // a "normal" binop gets parenthesized. (`LOr` is the lowest-precedence binop.) - let fake_prec = AssocOp::LOr.precedence() as i8; + let fake_prec = ExprPrecedence::LOr; if let Some(e) = start { self.print_expr_cond_paren( e, @@ -671,7 +661,7 @@ impl<'a> State<'a> { expr, // Parenthesize if required by precedence, or in the // case of `break 'inner: loop { break 'inner 1 } + 1` - expr.precedence() < parser::PREC_JUMP + expr.precedence() < ExprPrecedence::Jump || (opt_label.is_none() && classify::leading_labeled_expr(expr)), fixup.subsequent_subexpression(), ); @@ -690,7 +680,7 @@ impl<'a> State<'a> { self.word(" "); self.print_expr_cond_paren( expr, - expr.precedence() < parser::PREC_JUMP, + expr.precedence() < ExprPrecedence::Jump, fixup.subsequent_subexpression(), ); } @@ -703,7 +693,7 @@ impl<'a> State<'a> { self.word(" "); self.print_expr_cond_paren( expr, - expr.precedence() < parser::PREC_JUMP, + expr.precedence() < ExprPrecedence::Jump, fixup.subsequent_subexpression(), ); } @@ -713,7 +703,7 @@ impl<'a> State<'a> { self.word(" "); self.print_expr_cond_paren( result, - result.precedence() < parser::PREC_JUMP, + result.precedence() < ExprPrecedence::Jump, fixup.subsequent_subexpression(), ); } @@ -767,13 +757,13 @@ impl<'a> State<'a> { self.space(); self.print_expr_cond_paren( expr, - expr.precedence() < parser::PREC_JUMP, + expr.precedence() < ExprPrecedence::Jump, fixup.subsequent_subexpression(), ); } } ast::ExprKind::Try(e) => { - self.print_expr_cond_paren(e, e.precedence() < parser::PREC_UNAMBIGUOUS, fixup); + self.print_expr_cond_paren(e, e.precedence() < ExprPrecedence::Unambiguous, fixup); self.word("?") } ast::ExprKind::TryBlock(blk) => { diff --git a/compiler/rustc_errors/src/diagnostic_impls.rs b/compiler/rustc_errors/src/diagnostic_impls.rs index 798668b8bc1f0..b451037132397 100644 --- a/compiler/rustc_errors/src/diagnostic_impls.rs +++ b/compiler/rustc_errors/src/diagnostic_impls.rs @@ -6,6 +6,7 @@ use std::path::{Path, PathBuf}; use std::process::ExitStatus; use rustc_abi::TargetDataLayoutErrors; +use rustc_ast::util::parser::ExprPrecedence; use rustc_ast_pretty::pprust; use rustc_macros::Subdiagnostic; use rustc_span::Span; @@ -298,6 +299,12 @@ impl IntoDiagArg for hir::def::Namespace { } } +impl IntoDiagArg for ExprPrecedence { + fn into_diag_arg(self) -> DiagArgValue { + DiagArgValue::Number(self as i32) + } +} + #[derive(Clone)] pub struct DiagSymbolList(Vec); diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 97aa88c1de8db..a9696627f11b1 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1,7 +1,7 @@ use std::fmt; use rustc_abi::ExternAbi; -use rustc_ast::util::parser::{AssocOp, PREC_CLOSURE, PREC_JUMP, PREC_PREFIX, PREC_UNAMBIGUOUS}; +use rustc_ast::util::parser::{AssocOp, ExprPrecedence}; use rustc_ast::{ self as ast, Attribute, FloatTy, InlineAsmOptions, InlineAsmTemplatePiece, IntTy, Label, LitKind, TraitObjectSyntax, UintTy, @@ -1695,22 +1695,22 @@ pub struct Expr<'hir> { } impl Expr<'_> { - pub fn precedence(&self) -> i8 { + pub fn precedence(&self) -> ExprPrecedence { match self.kind { - ExprKind::Closure { .. } => PREC_CLOSURE, + ExprKind::Closure { .. } => ExprPrecedence::Closure, ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) | ExprKind::Yield(..) - | ExprKind::Become(..) => PREC_JUMP, + | ExprKind::Become(..) => ExprPrecedence::Jump, // Binop-like expr kinds, handled by `AssocOp`. - ExprKind::Binary(op, ..) => AssocOp::from_ast_binop(op.node).precedence() as i8, - ExprKind::Cast(..) => AssocOp::As.precedence() as i8, + ExprKind::Binary(op, ..) => AssocOp::from_ast_binop(op.node).precedence(), + ExprKind::Cast(..) => ExprPrecedence::Cast, ExprKind::Assign(..) | - ExprKind::AssignOp(..) => AssocOp::Assign.precedence() as i8, + ExprKind::AssignOp(..) => ExprPrecedence::Assign, // Unary, prefix ExprKind::AddrOf(..) @@ -1719,7 +1719,7 @@ impl Expr<'_> { // need parens sometimes. E.g. we can print `(let _ = a) && b` as `let _ = a && b` // but we need to print `(let _ = a) < b` as-is with parens. | ExprKind::Let(..) - | ExprKind::Unary(..) => PREC_PREFIX, + | ExprKind::Unary(..) => ExprPrecedence::Prefix, // Never need parens ExprKind::Array(_) @@ -1740,7 +1740,7 @@ impl Expr<'_> { | ExprKind::Struct(..) | ExprKind::Tup(_) | ExprKind::Type(..) - | ExprKind::Err(_) => PREC_UNAMBIGUOUS, + | ExprKind::Err(_) => ExprPrecedence::Unambiguous, ExprKind::DropTemps(ref expr, ..) => expr.precedence(), } diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index e1f4ccca97e94..022a6d457ec56 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -103,7 +103,7 @@ fn allowed_union_or_unsafe_field<'tcx>( // Fallback case: allow `ManuallyDrop` and things that are `Copy`, // also no need to report an error if the type is unresolved. ty.ty_adt_def().is_some_and(|adt_def| adt_def.is_manually_drop()) - || ty.is_copy_modulo_regions(tcx, typing_env) + || tcx.type_is_copy_modulo_regions(typing_env, ty) || ty.references_error() } }; diff --git a/compiler/rustc_hir_analysis/src/check/intrinsicck.rs b/compiler/rustc_hir_analysis/src/check/intrinsicck.rs index b96469f503c8b..df4da03f0f59d 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsicck.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsicck.rs @@ -178,7 +178,7 @@ impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> { // Check that the type implements Copy. The only case where this can // possibly fail is for SIMD types which don't #[derive(Copy)]. - if !ty.is_copy_modulo_regions(self.tcx, self.typing_env) { + if !self.tcx.type_is_copy_modulo_regions(self.typing_env, ty) { let msg = "arguments for inline assembly must be copyable"; self.tcx .dcx() diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index 11864b61a94e7..0f3dcebc092ef 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -10,7 +10,7 @@ use std::cell::Cell; use std::vec; use rustc_abi::ExternAbi; -use rustc_ast::util::parser::{self, AssocOp, Fixity}; +use rustc_ast::util::parser::{self, AssocOp, ExprPrecedence, Fixity}; use rustc_ast_pretty::pp::Breaks::{Consistent, Inconsistent}; use rustc_ast_pretty::pp::{self, Breaks}; use rustc_ast_pretty::pprust::{Comments, PrintState}; @@ -1125,12 +1125,12 @@ impl<'a> State<'a> { } fn print_expr_call(&mut self, func: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - let prec = match func.kind { - hir::ExprKind::Field(..) => parser::PREC_FORCE_PAREN, - _ => parser::PREC_UNAMBIGUOUS, + let needs_paren = match func.kind { + hir::ExprKind::Field(..) => true, + _ => func.precedence() < ExprPrecedence::Unambiguous, }; - self.print_expr_cond_paren(func, func.precedence() < prec); + self.print_expr_cond_paren(func, needs_paren); self.print_call_post(args) } @@ -1141,7 +1141,7 @@ impl<'a> State<'a> { args: &[hir::Expr<'_>], ) { let base_args = args; - self.print_expr_cond_paren(receiver, receiver.precedence() < parser::PREC_UNAMBIGUOUS); + self.print_expr_cond_paren(receiver, receiver.precedence() < ExprPrecedence::Unambiguous); self.word("."); self.print_ident(segment.ident); @@ -1155,37 +1155,38 @@ impl<'a> State<'a> { fn print_expr_binary(&mut self, op: hir::BinOp, lhs: &hir::Expr<'_>, rhs: &hir::Expr<'_>) { let assoc_op = AssocOp::from_ast_binop(op.node); - let prec = assoc_op.precedence() as i8; - let fixity = assoc_op.fixity(); - - let (left_prec, right_prec) = match fixity { - Fixity::Left => (prec, prec + 1), - Fixity::Right => (prec + 1, prec), - Fixity::None => (prec + 1, prec + 1), + let binop_prec = assoc_op.precedence(); + let left_prec = lhs.precedence(); + let right_prec = rhs.precedence(); + + let (mut left_needs_paren, right_needs_paren) = match assoc_op.fixity() { + Fixity::Left => (left_prec < binop_prec, right_prec <= binop_prec), + Fixity::Right => (left_prec <= binop_prec, right_prec < binop_prec), + Fixity::None => (left_prec <= binop_prec, right_prec <= binop_prec), }; - let left_prec = match (&lhs.kind, op.node) { + match (&lhs.kind, op.node) { // These cases need parens: `x as i32 < y` has the parser thinking that `i32 < y` is // the beginning of a path type. It starts trying to parse `x as (i32 < y ...` instead // of `(x as i32) < ...`. We need to convince it _not_ to do that. (&hir::ExprKind::Cast { .. }, hir::BinOpKind::Lt | hir::BinOpKind::Shl) => { - parser::PREC_FORCE_PAREN + left_needs_paren = true; } - (&hir::ExprKind::Let { .. }, _) if !parser::needs_par_as_let_scrutinee(prec) => { - parser::PREC_FORCE_PAREN + (&hir::ExprKind::Let { .. }, _) if !parser::needs_par_as_let_scrutinee(binop_prec) => { + left_needs_paren = true; } - _ => left_prec, - }; + _ => {} + } - self.print_expr_cond_paren(lhs, lhs.precedence() < left_prec); + self.print_expr_cond_paren(lhs, left_needs_paren); self.space(); self.word_space(op.node.as_str()); - self.print_expr_cond_paren(rhs, rhs.precedence() < right_prec) + self.print_expr_cond_paren(rhs, right_needs_paren); } fn print_expr_unary(&mut self, op: hir::UnOp, expr: &hir::Expr<'_>) { self.word(op.as_str()); - self.print_expr_cond_paren(expr, expr.precedence() < parser::PREC_PREFIX) + self.print_expr_cond_paren(expr, expr.precedence() < ExprPrecedence::Prefix); } fn print_expr_addr_of( @@ -1202,7 +1203,7 @@ impl<'a> State<'a> { self.print_mutability(mutability, true); } } - self.print_expr_cond_paren(expr, expr.precedence() < parser::PREC_PREFIX) + self.print_expr_cond_paren(expr, expr.precedence() < ExprPrecedence::Prefix); } fn print_literal(&mut self, lit: &hir::Lit) { @@ -1340,8 +1341,7 @@ impl<'a> State<'a> { self.print_literal(lit); } hir::ExprKind::Cast(expr, ty) => { - let prec = AssocOp::As.precedence() as i8; - self.print_expr_cond_paren(expr, expr.precedence() < prec); + self.print_expr_cond_paren(expr, expr.precedence() < ExprPrecedence::Cast); self.space(); self.word_space("as"); self.print_type(ty); @@ -1442,27 +1442,25 @@ impl<'a> State<'a> { self.print_block(blk); } hir::ExprKind::Assign(lhs, rhs, _) => { - let prec = AssocOp::Assign.precedence() as i8; - self.print_expr_cond_paren(lhs, lhs.precedence() <= prec); + self.print_expr_cond_paren(lhs, lhs.precedence() <= ExprPrecedence::Assign); self.space(); self.word_space("="); - self.print_expr_cond_paren(rhs, rhs.precedence() < prec); + self.print_expr_cond_paren(rhs, rhs.precedence() < ExprPrecedence::Assign); } hir::ExprKind::AssignOp(op, lhs, rhs) => { - let prec = AssocOp::Assign.precedence() as i8; - self.print_expr_cond_paren(lhs, lhs.precedence() <= prec); + self.print_expr_cond_paren(lhs, lhs.precedence() <= ExprPrecedence::Assign); self.space(); self.word(op.node.as_str()); self.word_space("="); - self.print_expr_cond_paren(rhs, rhs.precedence() < prec); + self.print_expr_cond_paren(rhs, rhs.precedence() < ExprPrecedence::Assign); } hir::ExprKind::Field(expr, ident) => { - self.print_expr_cond_paren(expr, expr.precedence() < parser::PREC_UNAMBIGUOUS); + self.print_expr_cond_paren(expr, expr.precedence() < ExprPrecedence::Unambiguous); self.word("."); self.print_ident(ident); } hir::ExprKind::Index(expr, index, _) => { - self.print_expr_cond_paren(expr, expr.precedence() < parser::PREC_UNAMBIGUOUS); + self.print_expr_cond_paren(expr, expr.precedence() < ExprPrecedence::Unambiguous); self.word("["); self.print_expr(index); self.word("]"); @@ -1476,7 +1474,7 @@ impl<'a> State<'a> { } if let Some(expr) = opt_expr { self.space(); - self.print_expr_cond_paren(expr, expr.precedence() < parser::PREC_JUMP); + self.print_expr_cond_paren(expr, expr.precedence() < ExprPrecedence::Jump); } } hir::ExprKind::Continue(destination) => { @@ -1490,13 +1488,13 @@ impl<'a> State<'a> { self.word("return"); if let Some(expr) = result { self.word(" "); - self.print_expr_cond_paren(expr, expr.precedence() < parser::PREC_JUMP); + self.print_expr_cond_paren(expr, expr.precedence() < ExprPrecedence::Jump); } } hir::ExprKind::Become(result) => { self.word("become"); self.word(" "); - self.print_expr_cond_paren(result, result.precedence() < parser::PREC_JUMP); + self.print_expr_cond_paren(result, result.precedence() < ExprPrecedence::Jump); } hir::ExprKind::InlineAsm(asm) => { self.word("asm!"); @@ -1521,7 +1519,7 @@ impl<'a> State<'a> { } hir::ExprKind::Yield(expr, _) => { self.word_space("yield"); - self.print_expr_cond_paren(expr, expr.precedence() < parser::PREC_JUMP); + self.print_expr_cond_paren(expr, expr.precedence() < ExprPrecedence::Jump); } hir::ExprKind::Err(_) => { self.popen(); diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index b0afbab8e01b8..b430f48965a88 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -1,6 +1,6 @@ use std::iter; -use rustc_ast::util::parser::PREC_UNAMBIGUOUS; +use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::{Applicability, Diag, ErrorGuaranteed, StashKey}; use rustc_hir::def::{self, CtorKind, Namespace, Res}; use rustc_hir::def_id::DefId; @@ -606,7 +606,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; if let Ok(rest_snippet) = rest_snippet { - let sugg = if callee_expr.precedence() >= PREC_UNAMBIGUOUS { + let sugg = if callee_expr.precedence() >= ExprPrecedence::Unambiguous { vec![ (up_to_rcvr_span, "".to_string()), (rest_span, format!(".{}({rest_snippet}", segment.ident)), diff --git a/compiler/rustc_hir_typeck/src/cast.rs b/compiler/rustc_hir_typeck/src/cast.rs index 0c3f21d540dcd..80b91c215980a 100644 --- a/compiler/rustc_hir_typeck/src/cast.rs +++ b/compiler/rustc_hir_typeck/src/cast.rs @@ -28,6 +28,7 @@ //! expression, `e as U2` is not necessarily so (in fact it will only be valid if //! `U1` coerces to `U2`). +use rustc_ast::util::parser::ExprPrecedence; use rustc_data_structures::fx::FxHashSet; use rustc_errors::codes::*; use rustc_errors::{Applicability, Diag, ErrorGuaranteed}; @@ -1108,7 +1109,7 @@ impl<'a, 'tcx> CastCheck<'tcx> { fn lossy_provenance_ptr2int_lint(&self, fcx: &FnCtxt<'a, 'tcx>, t_c: ty::cast::IntTy) { let expr_prec = self.expr.precedence(); - let needs_parens = expr_prec < rustc_ast::util::parser::PREC_UNAMBIGUOUS; + let needs_parens = expr_prec < ExprPrecedence::Unambiguous; let needs_cast = !matches!(t_c, ty::cast::IntTy::U(ty::UintTy::Usize)); let cast_span = self.expr_span.shrink_to_hi().to(self.cast_span); diff --git a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs index 2a00530c43436..774d00edea035 100644 --- a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs +++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs @@ -228,7 +228,7 @@ impl<'tcx> TypeInformationCtxt<'tcx> for (&LateContext<'tcx>, LocalDefId) { } fn type_is_copy_modulo_regions(&self, ty: Ty<'tcx>) -> bool { - ty.is_copy_modulo_regions(self.0.tcx, self.0.typing_env()) + self.0.type_is_copy_modulo_regions(ty) } fn body_owner_def_id(&self) -> LocalDefId { diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index b493a61b9f44d..61260dbd16c51 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -2,7 +2,7 @@ use core::cmp::min; use core::iter; use hir::def_id::LocalDefId; -use rustc_ast::util::parser::PREC_UNAMBIGUOUS; +use rustc_ast::util::parser::ExprPrecedence; use rustc_data_structures::packed::Pu128; use rustc_errors::{Applicability, Diag, MultiSpan}; use rustc_hir as hir; @@ -398,7 +398,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // so we remove the user's `clone` call. { vec![(receiver_method.ident.span, conversion_method.name.to_string())] - } else if expr.precedence() < PREC_UNAMBIGUOUS { + } else if expr.precedence() < ExprPrecedence::Unambiguous { vec![ (expr.span.shrink_to_lo(), "(".to_string()), (expr.span.shrink_to_hi(), format!(").{}()", conversion_method.name)), @@ -1376,7 +1376,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { { let span = expr.span.find_oldest_ancestor_in_same_ctxt(); - let mut sugg = if expr.precedence() >= PREC_UNAMBIGUOUS { + let mut sugg = if expr.precedence() >= ExprPrecedence::Unambiguous { vec![(span.shrink_to_hi(), ".into()".to_owned())] } else { vec![ @@ -3000,7 +3000,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { "change the type of the numeric literal from `{checked_ty}` to `{expected_ty}`", ); - let close_paren = if expr.precedence() < PREC_UNAMBIGUOUS { + let close_paren = if expr.precedence() < ExprPrecedence::Unambiguous { sugg.push((expr.span.shrink_to_lo(), "(".to_string())); ")" } else { @@ -3025,7 +3025,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let len = src.trim_end_matches(&checked_ty.to_string()).len(); expr.span.with_lo(expr.span.lo() + BytePos(len as u32)) }, - if expr.precedence() < PREC_UNAMBIGUOUS { + if expr.precedence() < ExprPrecedence::Unambiguous { // Readd `)` format!("{expected_ty})") } else { diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index e130cfc1d736d..ec08519892203 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -586,7 +586,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations { return; } } - if ty.is_copy_modulo_regions(cx.tcx, cx.typing_env()) { + if cx.type_is_copy_modulo_regions(ty) { return; } if type_implements_negative_copy_modulo_regions(cx.tcx, ty, cx.typing_env()) { diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index 7248b19276381..44c7888a530ca 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -710,6 +710,10 @@ impl<'tcx> LateContext<'tcx> { TypingEnv { typing_mode: self.typing_mode(), param_env: self.param_env } } + pub fn type_is_copy_modulo_regions(&self, ty: Ty<'tcx>) -> bool { + self.tcx.type_is_copy_modulo_regions(self.typing_env(), ty) + } + /// Gets the type-checking results for the current body, /// or `None` if outside a body. pub fn maybe_typeck_results(&self) -> Option<&'tcx ty::TypeckResults<'tcx>> { diff --git a/compiler/rustc_lint/src/drop_forget_useless.rs b/compiler/rustc_lint/src/drop_forget_useless.rs index 8fe8673865887..ce23892508b5d 100644 --- a/compiler/rustc_lint/src/drop_forget_useless.rs +++ b/compiler/rustc_lint/src/drop_forget_useless.rs @@ -144,7 +144,7 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetUseless { && let Some(fn_name) = cx.tcx.get_diagnostic_name(def_id) { let arg_ty = cx.typeck_results().expr_ty(arg); - let is_copy = arg_ty.is_copy_modulo_regions(cx.tcx, cx.typing_env()); + let is_copy = cx.type_is_copy_modulo_regions(arg_ty); let drop_is_single_call_in_arm = is_single_call_in_arm(cx, arg, expr); let let_underscore_ignore_sugg = || { if let Some((_, node)) = cx.tcx.hir().parent_iter(expr.hir_id).nth(0) diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs index 38c38b59bc584..7c8a65059d117 100644 --- a/compiler/rustc_lint/src/internal.rs +++ b/compiler/rustc_lint/src/internal.rs @@ -242,17 +242,10 @@ fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &Path<'_>) -> Option { } // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait. Res::SelfTyAlias { alias_to: did, is_trait_impl: false, .. } => { - if let ty::Adt(adt, args) = cx.tcx.type_of(did).instantiate_identity().kind() { - if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(adt.did()) - { - // NOTE: This path is currently unreachable as `Ty<'tcx>` is - // defined as a type alias meaning that `impl<'tcx> Ty<'tcx>` - // is not actually allowed. - // - // I(@lcnr) still kept this branch in so we don't miss this - // if we ever change it in the future. - return Some(format!("{}<{}>", name, args[0])); - } + if let ty::Adt(adt, args) = cx.tcx.type_of(did).instantiate_identity().kind() + && let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(adt.did()) + { + return Some(format!("{}<{}>", name, args[0])); } } _ => (), diff --git a/compiler/rustc_middle/src/ty/util.rs b/compiler/rustc_middle/src/ty/util.rs index 797ac9685b1e0..57054bd1a0b24 100644 --- a/compiler/rustc_middle/src/ty/util.rs +++ b/compiler/rustc_middle/src/ty/util.rs @@ -172,6 +172,24 @@ impl<'tcx> TyCtxt<'tcx> { } } + /// Checks whether `ty: Copy` holds while ignoring region constraints. + /// + /// This impacts whether values of `ty` are *moved* or *copied* + /// when referenced. This means that we may generate MIR which + /// does copies even when the type actually doesn't satisfy the + /// full requirements for the `Copy` trait (cc #29149) -- this + /// winds up being reported as an error during NLL borrow check. + /// + /// This function should not be used if there is an `InferCtxt` available. + /// Use `InferCtxt::type_is_copy_modulo_regions` instead. + pub fn type_is_copy_modulo_regions( + self, + typing_env: ty::TypingEnv<'tcx>, + ty: Ty<'tcx>, + ) -> bool { + ty.is_trivially_pure_clone_copy() || self.is_copy_raw(typing_env.as_query_input(ty)) + } + /// Returns the deeply last field of nested structures, or the same type if /// not a structure at all. Corresponds to the only possible unsized field, /// and its type can be used to determine unsizing strategy. @@ -1174,21 +1192,6 @@ impl<'tcx> Ty<'tcx> { .map(|(min, _)| ty::Const::from_bits(tcx, min, typing_env, self)) } - /// Checks whether values of this type `T` are *moved* or *copied* - /// when referenced -- this amounts to a check for whether `T: - /// Copy`, but note that we **don't** consider lifetimes when - /// doing this check. This means that we may generate MIR which - /// does copies even when the type actually doesn't satisfy the - /// full requirements for the `Copy` trait (cc #29149) -- this - /// winds up being reported as an error during NLL borrow check. - pub fn is_copy_modulo_regions( - self, - tcx: TyCtxt<'tcx>, - typing_env: ty::TypingEnv<'tcx>, - ) -> bool { - self.is_trivially_pure_clone_copy() || tcx.is_copy_raw(typing_env.as_query_input(self)) - } - /// Checks whether values of this type `T` have a size known at /// compile time (i.e., whether `T: Sized`). Lifetimes are ignored /// for the purposes of this check, so it can be an diff --git a/compiler/rustc_mir_build/src/build/expr/as_operand.rs b/compiler/rustc_mir_build/src/build/expr/as_operand.rs index 0cab853196b8e..777ff9e68f029 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_operand.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_operand.rs @@ -176,7 +176,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let ty = expr.ty; if !ty.is_sized(tcx, this.typing_env()) { // !sized means !copy, so this is an unsized move - assert!(!ty.is_copy_modulo_regions(tcx, this.typing_env())); + assert!(!tcx.type_is_copy_modulo_regions(this.typing_env(), ty)); // As described above, detect the case where we are passing a value of unsized // type, and that value is coming from the deref of a box. diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index e55cd35e2436d..a13b00e192167 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -780,7 +780,7 @@ fn check_borrow_conflicts_in_at_patterns<'tcx>(cx: &MatchVisitor<'_, 'tcx>, pat: return; }; - let is_binding_by_move = |ty: Ty<'tcx>| !ty.is_copy_modulo_regions(cx.tcx, cx.typing_env); + let is_binding_by_move = |ty: Ty<'tcx>| !cx.tcx.type_is_copy_modulo_regions(cx.typing_env, ty); let sess = cx.tcx.sess; diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs index 0878fa26a92c6..79c38b50459c4 100644 --- a/compiler/rustc_mir_transform/src/inline.rs +++ b/compiler/rustc_mir_transform/src/inline.rs @@ -220,15 +220,7 @@ impl<'tcx> Inliner<'tcx> { // Normally, this shouldn't be required, but trait normalization failure can create a // validation ICE. - if !validate_types( - self.tcx, - MirPhase::Runtime(RuntimePhase::Optimized), - self.typing_env, - &callee_body, - &caller_body, - ) - .is_empty() - { + if !validate_types(self.tcx, self.typing_env, &callee_body, &caller_body).is_empty() { return Err("failed to validate callee body"); } diff --git a/compiler/rustc_mir_transform/src/pass_manager.rs b/compiler/rustc_mir_transform/src/pass_manager.rs index 779e7f22101c2..8a45ce0762d59 100644 --- a/compiler/rustc_mir_transform/src/pass_manager.rs +++ b/compiler/rustc_mir_transform/src/pass_manager.rs @@ -292,7 +292,7 @@ fn run_passes_inner<'tcx>( } pub(super) fn validate_body<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, when: String) { - validate::Validator { when, mir_phase: body.phase }.run_pass(tcx, body); + validate::Validator { when }.run_pass(tcx, body); } fn dump_mir_for_pass<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, pass_name: &str, is_after: bool) { diff --git a/compiler/rustc_mir_transform/src/validate.rs b/compiler/rustc_mir_transform/src/validate.rs index 51e5c49cea104..404fbb6b83937 100644 --- a/compiler/rustc_mir_transform/src/validate.rs +++ b/compiler/rustc_mir_transform/src/validate.rs @@ -29,12 +29,6 @@ enum EdgeKind { pub(super) struct Validator { /// Describes at which point in the pipeline this validation is happening. pub when: String, - /// The phase for which we are upholding the dialect. If the given phase forbids a specific - /// element, this validator will now emit errors if that specific element is encountered. - /// Note that phases that change the dialect cause all *following* phases to check the - /// invariants of the new dialect. A phase that changes dialects never checks the new invariants - /// itself. - pub mir_phase: MirPhase, } impl<'tcx> crate::MirPass<'tcx> for Validator { @@ -46,11 +40,9 @@ impl<'tcx> crate::MirPass<'tcx> for Validator { if matches!(body.source.instance, InstanceKind::Intrinsic(..) | InstanceKind::Virtual(..)) { return; } - debug_assert_eq!(self.mir_phase, body.phase); let def_id = body.source.def_id(); - let mir_phase = body.phase; let typing_env = body.typing_env(tcx); - let can_unwind = if mir_phase <= MirPhase::Runtime(RuntimePhase::Initial) { + let can_unwind = if body.phase <= MirPhase::Runtime(RuntimePhase::Initial) { // In this case `AbortUnwindingCalls` haven't yet been executed. true } else if !tcx.def_kind(def_id).is_fn_like() { @@ -64,9 +56,7 @@ impl<'tcx> crate::MirPass<'tcx> for Validator { ty::Coroutine(..) => ExternAbi::Rust, // No need to do MIR validation on error bodies ty::Error(_) => return, - _ => { - span_bug!(body.span, "unexpected body ty: {:?} phase {:?}", body_ty, mir_phase) - } + _ => span_bug!(body.span, "unexpected body ty: {body_ty:?}"), }; ty::layout::fn_can_unwind(tcx, Some(def_id), body_abi) @@ -76,7 +66,6 @@ impl<'tcx> crate::MirPass<'tcx> for Validator { when: &self.when, body, tcx, - mir_phase, unwind_edge_count: 0, reachable_blocks: traversal::reachable_as_bitset(body), value_cache: FxHashSet::default(), @@ -86,7 +75,7 @@ impl<'tcx> crate::MirPass<'tcx> for Validator { cfg_checker.check_cleanup_control_flow(); // Also run the TypeChecker. - for (location, msg) in validate_types(tcx, self.mir_phase, typing_env, body, body) { + for (location, msg) in validate_types(tcx, typing_env, body, body) { cfg_checker.fail(location, msg); } @@ -107,7 +96,6 @@ struct CfgChecker<'a, 'tcx> { when: &'a str, body: &'a Body<'tcx>, tcx: TyCtxt<'tcx>, - mir_phase: MirPhase, unwind_edge_count: usize, reachable_blocks: BitSet, value_cache: FxHashSet, @@ -294,7 +282,7 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> { fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { match &statement.kind { StatementKind::AscribeUserType(..) => { - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { self.fail( location, "`AscribeUserType` should have been removed after drop lowering phase", @@ -302,7 +290,7 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> { } } StatementKind::FakeRead(..) => { - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { self.fail( location, "`FakeRead` should have been removed after drop lowering phase", @@ -310,17 +298,17 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> { } } StatementKind::SetDiscriminant { .. } => { - if self.mir_phase < MirPhase::Runtime(RuntimePhase::Initial) { + if self.body.phase < MirPhase::Runtime(RuntimePhase::Initial) { self.fail(location, "`SetDiscriminant`is not allowed until deaggregation"); } } StatementKind::Deinit(..) => { - if self.mir_phase < MirPhase::Runtime(RuntimePhase::Initial) { + if self.body.phase < MirPhase::Runtime(RuntimePhase::Initial) { self.fail(location, "`Deinit`is not allowed until deaggregation"); } } StatementKind::Retag(kind, _) => { - // FIXME(JakobDegen) The validator should check that `self.mir_phase < + // FIXME(JakobDegen) The validator should check that `self.body.phase < // DropsLowered`. However, this causes ICEs with generation of drop shims, which // seem to fail to set their `MirPhase` correctly. if matches!(kind, RetagKind::TwoPhase) { @@ -328,7 +316,7 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> { } } StatementKind::Coverage(kind) => { - if self.mir_phase >= MirPhase::Analysis(AnalysisPhase::PostCleanup) + if self.body.phase >= MirPhase::Analysis(AnalysisPhase::PostCleanup) && let CoverageKind::BlockMarker { .. } | CoverageKind::SpanMarker { .. } = kind { self.fail( @@ -391,7 +379,7 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> { // the return edge from the call. FIXME(tmiasko): Since this is a strictly code // generation concern, the code generation should be responsible for handling // it. - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Optimized) + if self.body.phase >= MirPhase::Runtime(RuntimePhase::Optimized) && self.is_critical_call_edge(target, unwind) { self.fail( @@ -440,7 +428,7 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> { if self.body.coroutine.is_none() { self.fail(location, "`Yield` cannot appear outside coroutine bodies"); } - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { self.fail(location, "`Yield` should have been replaced by coroutine lowering"); } self.check_edge(location, *resume, EdgeKind::Normal); @@ -449,7 +437,7 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> { } } TerminatorKind::FalseEdge { real_target, imaginary_target } => { - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { self.fail( location, "`FalseEdge` should have been removed after drop elaboration", @@ -459,7 +447,7 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> { self.check_edge(location, *imaginary_target, EdgeKind::Normal); } TerminatorKind::FalseUnwind { real_target, unwind } => { - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { self.fail( location, "`FalseUnwind` should have been removed after drop elaboration", @@ -478,7 +466,7 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> { if self.body.coroutine.is_none() { self.fail(location, "`CoroutineDrop` cannot appear outside coroutine bodies"); } - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { self.fail( location, "`CoroutineDrop` should have been replaced by coroutine lowering", @@ -532,13 +520,11 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> { /// `optimized_mir` is available. pub(super) fn validate_types<'tcx>( tcx: TyCtxt<'tcx>, - mir_phase: MirPhase, typing_env: ty::TypingEnv<'tcx>, body: &Body<'tcx>, caller_body: &Body<'tcx>, ) -> Vec<(Location, String)> { - let mut type_checker = - TypeChecker { body, caller_body, tcx, typing_env, mir_phase, failures: Vec::new() }; + let mut type_checker = TypeChecker { body, caller_body, tcx, typing_env, failures: Vec::new() }; type_checker.visit_body(body); type_checker.failures } @@ -548,7 +534,6 @@ struct TypeChecker<'a, 'tcx> { caller_body: &'a Body<'tcx>, tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>, - mir_phase: MirPhase, failures: Vec<(Location, String)>, } @@ -577,7 +562,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { // After borrowck subtyping should be fully explicit via // `Subtype` projections. - let variance = if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + let variance = if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { Variance::Invariant } else { Variance::Covariant @@ -618,13 +603,13 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) { // This check is somewhat expensive, so only run it when -Zvalidate-mir is passed. if self.tcx.sess.opts.unstable_opts.validate_mir - && self.mir_phase < MirPhase::Runtime(RuntimePhase::Initial) + && self.body.phase < MirPhase::Runtime(RuntimePhase::Initial) { // `Operand::Copy` is only supposed to be used with `Copy` types. if let Operand::Copy(place) = operand { let ty = place.ty(&self.body.local_decls, self.tcx).ty; - if !ty.is_copy_modulo_regions(self.tcx, self.typing_env) { + if !self.tcx.type_is_copy_modulo_regions(self.typing_env, ty) { self.fail(location, format!("`Operand::Copy` with non-`Copy` type {ty}")); } } @@ -642,7 +627,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { ) { match elem { ProjectionElem::OpaqueCast(ty) - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) => + if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) => { self.fail( location, @@ -656,7 +641,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } } ProjectionElem::Deref - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::PostCleanup) => + if self.body.phase >= MirPhase::Runtime(RuntimePhase::PostCleanup) => { let base_ty = place_ref.ty(&self.body.local_decls, self.tcx).ty; @@ -856,7 +841,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { // Set off any `bug!`s in the type computation code let _ = place.ty(&self.body.local_decls, self.tcx); - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) + if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) && place.projection.len() > 1 && cntxt != PlaceContext::NonUse(NonUseContext::VarDebugInfo) && place.projection[1..].contains(&ProjectionElem::Deref) @@ -974,7 +959,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } } AggregateKind::RawPtr(pointee_ty, mutability) => { - if !matches!(self.mir_phase, MirPhase::Runtime(_)) { + if !matches!(self.body.phase, MirPhase::Runtime(_)) { // It would probably be fine to support this in earlier phases, but at the // time of writing it's only ever introduced from intrinsic lowering, so // earlier things just `bug!` on it. @@ -1016,7 +1001,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } }, Rvalue::Ref(_, BorrowKind::Fake(_), _) => { - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { self.fail( location, "`Assign` statement with a `Fake` borrow should have been removed in runtime MIR", @@ -1130,7 +1115,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { ); } UnOp::PtrMetadata => { - if !matches!(self.mir_phase, MirPhase::Runtime(_)) { + if !matches!(self.body.phase, MirPhase::Runtime(_)) { // It would probably be fine to support this in earlier phases, but at // the time of writing it's only ever introduced from intrinsic // lowering or other runtime-phase optimization passes, so earlier @@ -1206,7 +1191,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { "CastKind::{kind:?} output must be a raw const pointer, not {:?}", ty::RawPtr(_, Mutability::Not) ); - if self.mir_phase >= MirPhase::Analysis(AnalysisPhase::PostCleanup) { + if self.body.phase >= MirPhase::Analysis(AnalysisPhase::PostCleanup) { self.fail(location, format!("After borrowck, MIR disallows {kind:?}")); } } @@ -1222,7 +1207,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { "CastKind::{kind:?} output must be a raw pointer, not {:?}", ty::RawPtr(..) ); - if self.mir_phase >= MirPhase::Analysis(AnalysisPhase::PostCleanup) { + if self.body.phase >= MirPhase::Analysis(AnalysisPhase::PostCleanup) { self.fail(location, format!("After borrowck, MIR disallows {kind:?}")); } } @@ -1288,7 +1273,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } } CastKind::Transmute => { - if let MirPhase::Runtime(..) = self.mir_phase { + if let MirPhase::Runtime(..) = self.body.phase { // Unlike `mem::transmute`, a MIR `Transmute` is well-formed // for any two `Sized` types, just potentially UB to run. @@ -1317,7 +1302,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { location, format!( "Transmute is not supported in non-runtime phase {:?}.", - self.mir_phase + self.body.phase ), ); } @@ -1404,7 +1389,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } } StatementKind::AscribeUserType(..) => { - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { self.fail( location, "`AscribeUserType` should have been removed after drop lowering phase", @@ -1412,7 +1397,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } } StatementKind::FakeRead(..) => { - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + if self.body.phase >= MirPhase::Runtime(RuntimePhase::Initial) { self.fail( location, "`FakeRead` should have been removed after drop lowering phase", @@ -1463,7 +1448,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } } StatementKind::SetDiscriminant { place, .. } => { - if self.mir_phase < MirPhase::Runtime(RuntimePhase::Initial) { + if self.body.phase < MirPhase::Runtime(RuntimePhase::Initial) { self.fail(location, "`SetDiscriminant`is not allowed until deaggregation"); } let pty = place.ty(&self.body.local_decls, self.tcx).ty.kind(); @@ -1477,12 +1462,12 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } } StatementKind::Deinit(..) => { - if self.mir_phase < MirPhase::Runtime(RuntimePhase::Initial) { + if self.body.phase < MirPhase::Runtime(RuntimePhase::Initial) { self.fail(location, "`Deinit`is not allowed until deaggregation"); } } StatementKind::Retag(kind, _) => { - // FIXME(JakobDegen) The validator should check that `self.mir_phase < + // FIXME(JakobDegen) The validator should check that `self.body.phase < // DropsLowered`. However, this causes ICEs with generation of drop shims, which // seem to fail to set their `MirPhase` correctly. if matches!(kind, RetagKind::TwoPhase) { diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index 91e8ba2e1f996..2579e4c1f2596 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use rustc_ast::token::Token; +use rustc_ast::util::parser::ExprPrecedence; use rustc_ast::{Path, Visibility}; use rustc_errors::codes::*; use rustc_errors::{ @@ -2686,7 +2687,7 @@ pub(crate) struct UnexpectedExpressionInPattern { /// Was a `RangePatternBound` expected? pub is_bound: bool, /// The unexpected expr's precedence (used in match arm guard suggestions). - pub expr_precedence: i8, + pub expr_precedence: ExprPrecedence, } #[derive(Subdiagnostic)] diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 4430d2d14313b..8d16d44b0a26a 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1,7 +1,7 @@ // ignore-tidy-filelength use core::mem; -use core::ops::ControlFlow; +use core::ops::{Bound, ControlFlow}; use ast::mut_visit::{self, MutVisitor}; use ast::token::IdentIsRaw; @@ -10,7 +10,7 @@ use rustc_ast::ptr::P; use rustc_ast::token::{self, Delimiter, Token, TokenKind}; use rustc_ast::util::case::Case; use rustc_ast::util::classify; -use rustc_ast::util::parser::{AssocOp, Fixity, prec_let_scrutinee_needs_par}; +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, @@ -120,7 +120,7 @@ impl<'a> Parser<'a> { r: Restrictions, attrs: AttrWrapper, ) -> PResult<'a, (P, bool)> { - self.with_res(r, |this| this.parse_expr_assoc_with(0, attrs)) + self.with_res(r, |this| this.parse_expr_assoc_with(Bound::Unbounded, attrs)) } /// Parses an associative expression with operators of at least `min_prec` precedence. @@ -128,7 +128,7 @@ impl<'a> Parser<'a> { /// followed by a subexpression (e.g. `1 + 2`). pub(super) fn parse_expr_assoc_with( &mut self, - min_prec: usize, + min_prec: Bound, attrs: AttrWrapper, ) -> PResult<'a, (P, bool)> { let lhs = if self.token.is_range_separator() { @@ -144,7 +144,7 @@ impl<'a> Parser<'a> { /// was actually parsed. pub(super) fn parse_expr_assoc_rest_with( &mut self, - min_prec: usize, + min_prec: Bound, starts_stmt: bool, mut lhs: P, ) -> PResult<'a, (P, bool)> { @@ -163,7 +163,11 @@ impl<'a> Parser<'a> { self.restrictions }; let prec = op.node.precedence(); - if prec < min_prec { + if match min_prec { + Bound::Included(min_prec) => prec < min_prec, + Bound::Excluded(min_prec) => prec <= min_prec, + Bound::Unbounded => false, + } { break; } // Check for deprecated `...` syntax @@ -276,16 +280,16 @@ impl<'a> Parser<'a> { } let fixity = op.fixity(); - let prec_adjustment = match fixity { - Fixity::Right => 0, - Fixity::Left => 1, + let min_prec = match fixity { + Fixity::Right => Bound::Included(prec), + Fixity::Left => Bound::Excluded(prec), // We currently have no non-associative operators that are not handled above by // the special cases. The code is here only for future convenience. - Fixity::None => 1, + Fixity::None => Bound::Excluded(prec), }; let (rhs, _) = self.with_res(restrictions - Restrictions::STMT_EXPR, |this| { let attrs = this.parse_outer_attributes()?; - this.parse_expr_assoc_with(prec + prec_adjustment, attrs) + this.parse_expr_assoc_with(min_prec, attrs) })?; let span = self.mk_expr_sp(&lhs, lhs_span, rhs.span); @@ -451,7 +455,7 @@ impl<'a> Parser<'a> { /// The other two variants are handled in `parse_prefix_range_expr` below. fn parse_expr_range( &mut self, - prec: usize, + prec: ExprPrecedence, lhs: P, op: AssocOp, cur_op_span: Span, @@ -460,7 +464,7 @@ impl<'a> Parser<'a> { let maybe_lt = self.token.clone(); let attrs = self.parse_outer_attributes()?; Some( - self.parse_expr_assoc_with(prec + 1, attrs) + self.parse_expr_assoc_with(Bound::Excluded(prec), attrs) .map_err(|err| self.maybe_err_dotdotlt_syntax(maybe_lt, err))? .0, ) @@ -518,7 +522,7 @@ impl<'a> Parser<'a> { let (span, opt_end) = if this.is_at_start_of_range_notation_rhs() { // RHS must be parsed with more associativity than the dots. let attrs = this.parse_outer_attributes()?; - this.parse_expr_assoc_with(op.unwrap().precedence() + 1, attrs) + this.parse_expr_assoc_with(Bound::Excluded(op.unwrap().precedence()), attrs) .map(|(x, _)| (lo.to(x.span), Some(x))) .map_err(|err| this.maybe_err_dotdotlt_syntax(maybe_lt, err))? } else { @@ -2643,7 +2647,8 @@ impl<'a> Parser<'a> { self.expect(&token::Eq)?; } let attrs = self.parse_outer_attributes()?; - let (expr, _) = self.parse_expr_assoc_with(1 + prec_let_scrutinee_needs_par(), attrs)?; + let (expr, _) = + self.parse_expr_assoc_with(Bound::Excluded(prec_let_scrutinee_needs_par()), attrs)?; let span = lo.to(expr.span); Ok(self.mk_expr(span, ExprKind::Let(pat, expr, span, recovered))) } diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index bb976e092bf4c..e08b925f00893 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -1,12 +1,13 @@ +use std::ops::Bound; + use rustc_ast::mut_visit::{self, MutVisitor}; use rustc_ast::ptr::P; use rustc_ast::token::{self, BinOpToken, Delimiter, IdentIsRaw, Token}; -use rustc_ast::util::parser::AssocOp; +use rustc_ast::util::parser::ExprPrecedence; use rustc_ast::visit::{self, Visitor}; use rustc_ast::{ - self as ast, Arm, AttrVec, BinOpKind, BindingMode, ByRef, Expr, ExprKind, LocalKind, MacCall, - Mutability, Pat, PatField, PatFieldsRest, PatKind, Path, QSelf, RangeEnd, RangeSyntax, Stmt, - StmtKind, + self as ast, Arm, AttrVec, BindingMode, ByRef, Expr, ExprKind, LocalKind, MacCall, Mutability, + Pat, PatField, PatFieldsRest, PatKind, Path, QSelf, RangeEnd, RangeSyntax, Stmt, StmtKind, }; use rustc_ast_pretty::pprust; use rustc_errors::{Applicability, Diag, DiagArgValue, PResult, StashKey}; @@ -435,8 +436,9 @@ impl<'a> Parser<'a> { // Parse an associative expression such as `+ expr`, `% expr`, ... // Assignments, ranges and `|` are disabled by [`Restrictions::IS_PAT`]. - let Ok((expr, _)) = - snapshot.parse_expr_assoc_rest_with(0, false, expr).map_err(|err| err.cancel()) + let Ok((expr, _)) = snapshot + .parse_expr_assoc_rest_with(Bound::Unbounded, false, expr) + .map_err(|err| err.cancel()) else { // We got a trailing method/operator, but that wasn't an expression. return None; @@ -545,10 +547,7 @@ impl<'a> Parser<'a> { // HACK: a neater way would be preferable. let expr = match &err.args["expr_precedence"] { DiagArgValue::Number(expr_precedence) => { - if *expr_precedence - <= AssocOp::from_ast_binop(BinOpKind::Eq).precedence() - as i32 - { + if *expr_precedence <= ExprPrecedence::Compare as i32 { format!("({expr})") } else { format!("{expr}") @@ -570,9 +569,7 @@ impl<'a> Parser<'a> { } Some(guard) => { // Are parentheses required around the old guard? - let wrap_guard = guard.precedence() - <= AssocOp::from_ast_binop(BinOpKind::And).precedence() - as i8; + let wrap_guard = guard.precedence() <= ExprPrecedence::LAnd; err.subdiagnostic( UnexpectedExpressionInPatternSugg::UpdateGuard { diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 190cd9ed0610f..5fa2e01fc863c 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; use std::mem; +use std::ops::Bound; use ast::Label; use rustc_ast as ast; @@ -207,7 +208,7 @@ impl<'a> Parser<'a> { // Perform this outside of the `collect_tokens` closure, since our // outer attributes do not apply to this part of the expression. let (expr, _) = self.with_res(Restrictions::STMT_EXPR, |this| { - this.parse_expr_assoc_rest_with(0, true, expr) + this.parse_expr_assoc_rest_with(Bound::Unbounded, true, expr) })?; Ok(self.mk_stmt(lo.to(self.prev_token.span), StmtKind::Expr(expr))) } else { @@ -240,7 +241,7 @@ impl<'a> Parser<'a> { let e = self.mk_expr(lo.to(hi), ExprKind::MacCall(mac)); let e = self.maybe_recover_from_bad_qpath(e)?; let e = self.parse_expr_dot_or_call_with(attrs, e, lo)?; - let (e, _) = self.parse_expr_assoc_rest_with(0, false, e)?; + let (e, _) = self.parse_expr_assoc_rest_with(Bound::Unbounded, false, e)?; StmtKind::Expr(e) }; Ok(self.mk_stmt(lo.to(hi), kind)) diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index f585410adb97d..120ae9946eac3 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1358,6 +1358,12 @@ pub struct EarlyDiagCtxt { dcx: DiagCtxt, } +impl Default for EarlyDiagCtxt { + fn default() -> Self { + Self::new(ErrorOutputType::default()) + } +} + impl EarlyDiagCtxt { pub fn new(output: ErrorOutputType) -> Self { let emitter = mk_emitter(output); diff --git a/compiler/rustc_trait_selection/src/infer.rs b/compiler/rustc_trait_selection/src/infer.rs index 43244eb5dcb12..ee708564a8046 100644 --- a/compiler/rustc_trait_selection/src/infer.rs +++ b/compiler/rustc_trait_selection/src/infer.rs @@ -35,7 +35,7 @@ impl<'tcx> InferCtxt<'tcx> { // FIXME(#132279): This should be removed as it causes us to incorrectly // handle opaques in their defining scope. if !(param_env, ty).has_infer() { - return ty.is_copy_modulo_regions(self.tcx, self.typing_env(param_env)); + return self.tcx.type_is_copy_modulo_regions(self.typing_env(param_env), ty); } let copy_def_id = self.tcx.require_lang_item(LangItem::Copy, None); diff --git a/compiler/rustc_ty_utils/src/needs_drop.rs b/compiler/rustc_ty_utils/src/needs_drop.rs index d85da7d355e6f..1c85eb2a86130 100644 --- a/compiler/rustc_ty_utils/src/needs_drop.rs +++ b/compiler/rustc_ty_utils/src/needs_drop.rs @@ -202,7 +202,7 @@ where } } - _ if component.is_copy_modulo_regions(tcx, self.typing_env) => {} + _ if tcx.type_is_copy_modulo_regions(self.typing_env, component) => {} ty::Closure(_, args) => { for upvar in args.as_closure().upvar_tys() { diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index a5507f0f33812..191eaccff9899 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -858,7 +858,7 @@ impl [T] { /// Gets a reference to the underlying array. /// - /// If `N` is not exactly equal to slice's the length of `self`, then this method returns `None`. + /// If `N` is not exactly equal to the length of `self`, then this method returns `None`. #[unstable(feature = "slice_as_array", issue = "133508")] #[inline] #[must_use] diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index d18673c8140da..5e3d06a4ae79a 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -32,12 +32,11 @@ use std::iter::Peekable; use std::ops::{ControlFlow, Range}; use std::path::PathBuf; use std::str::{self, CharIndices}; -use std::sync::OnceLock; use pulldown_cmark::{ BrokenLink, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag, TagEnd, html, }; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::FxHashMap; use rustc_errors::{Diag, DiagMessage}; use rustc_hir::def_id::LocalDefId; use rustc_middle::ty::TyCtxt; @@ -1882,66 +1881,63 @@ pub(crate) fn rust_code_blocks(md: &str, extra_info: &ExtraInfo<'_>) -> Vec, usize>, + map: FxHashMap, existing_footnotes: usize, } -// The map is pre-initialized and then can be used as is to prevent cloning it for each item -// (in the sidebar rendering). -static DEFAULT_ID_MAP: OnceLock> = OnceLock::new(); - -fn init_id_map() -> FxHashSet<&'static str> { - let mut map = FxHashSet::default(); - // This is the list of IDs used in JavaScript. - map.insert("help"); - map.insert("settings"); - map.insert("not-displayed"); - map.insert("alternative-display"); - map.insert("search"); - map.insert("crate-search"); - map.insert("crate-search-div"); - // This is the list of IDs used in HTML generated in Rust (including the ones - // used in tera template files). - map.insert("themeStyle"); - map.insert("settings-menu"); - map.insert("help-button"); - map.insert("sidebar-button"); - map.insert("main-content"); - map.insert("toggle-all-docs"); - map.insert("all-types"); - map.insert("default-settings"); - map.insert("sidebar-vars"); - map.insert("copy-path"); - map.insert("rustdoc-toc"); - map.insert("rustdoc-modnav"); - // This is the list of IDs used by rustdoc sections (but still generated by - // rustdoc). - map.insert("fields"); - map.insert("variants"); - map.insert("implementors-list"); - map.insert("synthetic-implementors-list"); - map.insert("foreign-impls"); - map.insert("implementations"); - map.insert("trait-implementations"); - map.insert("synthetic-implementations"); - map.insert("blanket-implementations"); - map.insert("required-associated-types"); - map.insert("provided-associated-types"); - map.insert("provided-associated-consts"); - map.insert("required-associated-consts"); - map.insert("required-methods"); - map.insert("provided-methods"); - map.insert("dyn-compatibility"); - map.insert("implementors"); - map.insert("synthetic-implementors"); - map.insert("implementations-list"); - map.insert("trait-implementations-list"); - map.insert("synthetic-implementations-list"); - map.insert("blanket-implementations-list"); - map.insert("deref-methods"); - map.insert("layout"); - map.insert("aliased-type"); - map +fn is_default_id(id: &str) -> bool { + matches!( + id, + // This is the list of IDs used in JavaScript. + "help" + | "settings" + | "not-displayed" + | "alternative-display" + | "search" + | "crate-search" + | "crate-search-div" + // This is the list of IDs used in HTML generated in Rust (including the ones + // used in tera template files). + | "themeStyle" + | "settings-menu" + | "help-button" + | "sidebar-button" + | "main-content" + | "toggle-all-docs" + | "all-types" + | "default-settings" + | "sidebar-vars" + | "copy-path" + | "rustdoc-toc" + | "rustdoc-modnav" + // This is the list of IDs used by rustdoc sections (but still generated by + // rustdoc). + | "fields" + | "variants" + | "implementors-list" + | "synthetic-implementors-list" + | "foreign-impls" + | "implementations" + | "trait-implementations" + | "synthetic-implementations" + | "blanket-implementations" + | "required-associated-types" + | "provided-associated-types" + | "provided-associated-consts" + | "required-associated-consts" + | "required-methods" + | "provided-methods" + | "dyn-compatibility" + | "implementors" + | "synthetic-implementors" + | "implementations-list" + | "trait-implementations-list" + | "synthetic-implementations-list" + | "blanket-implementations-list" + | "deref-methods" + | "layout" + | "aliased-type" + ) } impl IdMap { @@ -1953,7 +1949,7 @@ impl IdMap { let id = match self.map.get_mut(candidate.as_ref()) { None => { let candidate = candidate.to_string(); - if DEFAULT_ID_MAP.get_or_init(init_id_map).contains(candidate.as_str()) { + if is_default_id(&candidate) { let id = format!("{}-{}", candidate, 1); self.map.insert(candidate.into(), 2); id diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 1c8303d4c2087..bb967b7f163ed 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -321,8 +321,8 @@ fn from_clean_item(item: clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum { MethodItem(m, _) => ItemEnum::Function(from_function(m, true, header.unwrap(), renderer)), TyMethodItem(m) => ItemEnum::Function(from_function(m, false, header.unwrap(), renderer)), ImplItem(i) => ItemEnum::Impl((*i).into_json(renderer)), - StaticItem(s) => ItemEnum::Static(s.into_json(renderer)), - ForeignStaticItem(s, _) => ItemEnum::Static(s.into_json(renderer)), + StaticItem(s) => ItemEnum::Static(convert_static(s, rustc_hir::Safety::Safe, renderer)), + ForeignStaticItem(s, safety) => ItemEnum::Static(convert_static(s, safety, renderer)), ForeignTypeItem => ItemEnum::ExternType, TypeAliasItem(t) => ItemEnum::TypeAlias(t.into_json(renderer)), // FIXME(generic_const_items): Add support for generic free consts @@ -831,17 +831,20 @@ impl FromClean> for TypeAlias { } } -impl FromClean for Static { - fn from_clean(stat: clean::Static, renderer: &JsonRenderer<'_>) -> Self { - let tcx = renderer.tcx; - Static { - type_: (*stat.type_).into_json(renderer), - is_mutable: stat.mutability == ast::Mutability::Mut, - expr: stat - .expr - .map(|e| rendered_const(tcx, tcx.hir().body(e), tcx.hir().body_owner_def_id(e))) - .unwrap_or_default(), - } +fn convert_static( + stat: clean::Static, + safety: rustc_hir::Safety, + renderer: &JsonRenderer<'_>, +) -> Static { + let tcx = renderer.tcx; + Static { + type_: (*stat.type_).into_json(renderer), + is_mutable: stat.mutability == ast::Mutability::Mut, + is_unsafe: safety == rustc_hir::Safety::Unsafe, + expr: stat + .expr + .map(|e| rendered_const(tcx, tcx.hir().body(e), tcx.hir().body_owner_def_id(e))) + .unwrap_or_default(), } } diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs index f553a78d766e5..84b33e3d86068 100644 --- a/src/rustdoc-json-types/lib.rs +++ b/src/rustdoc-json-types/lib.rs @@ -30,7 +30,7 @@ pub type FxHashMap = HashMap; // re-export for use in src/librustdoc /// This integer is incremented with every breaking change to the API, /// and is returned along with the JSON blob as [`Crate::format_version`]. /// Consuming code should assert that this value matches the format version(s) that it supports. -pub const FORMAT_VERSION: u32 = 36; +pub const FORMAT_VERSION: u32 = 37; /// The root of the emitted JSON blob. /// @@ -1238,6 +1238,22 @@ pub struct Static { /// /// It's not guaranteed that it'll match the actual source code for the initial value. pub expr: String, + + /// Is the static `unsafe`? + /// + /// This is only true if it's in an `extern` block, and not explicity marked + /// as `safe`. + /// + /// ```rust + /// unsafe extern { + /// static A: i32; // unsafe + /// safe static B: i32; // safe + /// } + /// + /// static C: i32 = 0; // safe + /// static mut D: i32 = 0; // safe + /// ``` + pub is_unsafe: bool, } /// A primitive type declaration. Declarations of this kind can only come from the core library. diff --git a/src/tools/clippy/clippy_lints/src/dereference.rs b/src/tools/clippy/clippy_lints/src/dereference.rs index b1d192adff9a7..c449a1a875b9c 100644 --- a/src/tools/clippy/clippy_lints/src/dereference.rs +++ b/src/tools/clippy/clippy_lints/src/dereference.rs @@ -7,7 +7,7 @@ use clippy_utils::{ peel_middle_ty_refs, }; use core::mem; -use rustc_ast::util::parser::{PREC_PREFIX, PREC_UNAMBIGUOUS}; +use rustc_ast::util::parser::ExprPrecedence; use rustc_data_structures::fx::FxIndexMap; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; @@ -963,7 +963,7 @@ fn report<'tcx>( // expr_str (the suggestion) is never shown if is_final_ufcs is true, since it's // `expr.kind == ExprKind::Call`. Therefore, this is, afaik, always unnecessary. /* - expr_str = if !expr_is_macro_call && is_final_ufcs && expr.precedence() < PREC_PREFIX { + expr_str = if !expr_is_macro_call && is_final_ufcs && expr.precedence() < ExprPrecedence::Prefix { Cow::Owned(format!("({expr_str})")) } else { expr_str @@ -999,13 +999,13 @@ fn report<'tcx>( data.first_expr.span, state.msg, |diag| { - let (precedence, calls_field) = match cx.tcx.parent_hir_node(data.first_expr.hir_id) { + let needs_paren = match cx.tcx.parent_hir_node(data.first_expr.hir_id) { Node::Expr(e) => match e.kind { - ExprKind::Call(callee, _) if callee.hir_id != data.first_expr.hir_id => (0, false), - ExprKind::Call(..) => (PREC_UNAMBIGUOUS, matches!(expr.kind, ExprKind::Field(..))), - _ => (e.precedence(), false), + ExprKind::Call(callee, _) if callee.hir_id != data.first_expr.hir_id => false, + ExprKind::Call(..) => expr.precedence() < ExprPrecedence::Unambiguous || matches!(expr.kind, ExprKind::Field(..)), + _ => expr.precedence() < e.precedence(), }, - _ => (0, false), + _ => false, }; let is_in_tuple = matches!( get_parent_expr(cx, data.first_expr), @@ -1016,7 +1016,7 @@ fn report<'tcx>( ); let sugg = if !snip_is_macro - && (calls_field || expr.precedence() < precedence) + && needs_paren && !has_enclosing_paren(&snip) && !is_in_tuple { @@ -1049,16 +1049,16 @@ fn report<'tcx>( } } - let (prefix, precedence) = match mutability { + let (prefix, needs_paren) = match mutability { Some(mutability) if !ty.is_ref() => { let prefix = match mutability { Mutability::Not => "&", Mutability::Mut => "&mut ", }; - (prefix, PREC_PREFIX) + (prefix, expr.precedence() < ExprPrecedence::Prefix) }, - None if !ty.is_ref() && data.adjusted_ty.is_ref() => ("&", 0), - _ => ("", 0), + None if !ty.is_ref() && data.adjusted_ty.is_ref() => ("&", false), + _ => ("", false), }; span_lint_hir_and_then( cx, @@ -1070,7 +1070,7 @@ fn report<'tcx>( let mut app = Applicability::MachineApplicable; let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, data.first_expr.span.ctxt(), "..", &mut app); - let sugg = if !snip_is_macro && expr.precedence() < precedence && !has_enclosing_paren(&snip) { + let sugg = if !snip_is_macro && needs_paren && !has_enclosing_paren(&snip) { format!("{prefix}({snip})") } else { format!("{prefix}{snip}") @@ -1157,7 +1157,7 @@ impl<'tcx> Dereferencing<'tcx> { }, Some(parent) if !parent.span.from_expansion() => { // Double reference might be needed at this point. - if parent.precedence() == PREC_UNAMBIGUOUS { + if parent.precedence() == ExprPrecedence::Unambiguous { // Parentheses would be needed here, don't lint. *outer_pat = None; } else { diff --git a/src/tools/clippy/clippy_lints/src/loops/single_element_loop.rs b/src/tools/clippy/clippy_lints/src/loops/single_element_loop.rs index 35dc8e9aa4e24..12719c4f94bfd 100644 --- a/src/tools/clippy/clippy_lints/src/loops/single_element_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/single_element_loop.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::{indent_of, snippet, snippet_with_applicability}; use clippy_utils::visitors::contains_break_or_continue; use rustc_ast::Mutability; -use rustc_ast::util::parser::PREC_PREFIX; +use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, Pat, PatKind, is_range_literal}; use rustc_lint::LateContext; @@ -84,7 +84,7 @@ pub(super) fn check<'tcx>( if !prefix.is_empty() && ( // Precedence of internal expression is less than or equal to precedence of `&expr`. - arg_expression.precedence() <= PREC_PREFIX || is_range_literal(arg_expression) + arg_expression.precedence() <= ExprPrecedence::Prefix || is_range_literal(arg_expression) ) { arg_snip = format!("({arg_snip})").into(); diff --git a/src/tools/clippy/clippy_lints/src/matches/manual_utils.rs b/src/tools/clippy/clippy_lints/src/matches/manual_utils.rs index 9c6df4d8ac0d9..bac5cf88cfbf8 100644 --- a/src/tools/clippy/clippy_lints/src/matches/manual_utils.rs +++ b/src/tools/clippy/clippy_lints/src/matches/manual_utils.rs @@ -7,7 +7,7 @@ use clippy_utils::{ CaptureKind, can_move_expr_to_closure, is_else_clause, is_lint_allowed, is_res_lang_ctor, path_res, path_to_local_id, peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, }; -use rustc_ast::util::parser::PREC_UNAMBIGUOUS; +use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::def::Res; @@ -117,7 +117,7 @@ where // it's being passed by value. let scrutinee = peel_hir_expr_refs(scrutinee).0; let (scrutinee_str, _) = snippet_with_context(cx, scrutinee.span, expr_ctxt, "..", &mut app); - let scrutinee_str = if scrutinee.span.eq_ctxt(expr.span) && scrutinee.precedence() < PREC_UNAMBIGUOUS { + let scrutinee_str = if scrutinee.span.eq_ctxt(expr.span) && scrutinee.precedence() < ExprPrecedence::Unambiguous { format!("({scrutinee_str})") } else { scrutinee_str.into() diff --git a/src/tools/clippy/clippy_lints/src/methods/manual_inspect.rs b/src/tools/clippy/clippy_lints/src/methods/manual_inspect.rs index 865a42b65c624..20e4d233525a1 100644 --- a/src/tools/clippy/clippy_lints/src/methods/manual_inspect.rs +++ b/src/tools/clippy/clippy_lints/src/methods/manual_inspect.rs @@ -148,7 +148,7 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: _ => {}, } } - requires_copy |= !ty.is_copy_modulo_regions(cx.tcx, cx.typing_env()); + requires_copy |= !cx.type_is_copy_modulo_regions(ty); break; } }, @@ -158,7 +158,7 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: } if can_lint - && (!requires_copy || arg_ty.is_copy_modulo_regions(cx.tcx, cx.typing_env())) + && (!requires_copy || cx.type_is_copy_modulo_regions(arg_ty)) // This case could be handled, but a fair bit of care would need to be taken. && (!requires_deref || arg_ty.is_freeze(cx.tcx, cx.typing_env())) { diff --git a/src/tools/clippy/clippy_lints/src/neg_multiply.rs b/src/tools/clippy/clippy_lints/src/neg_multiply.rs index a0ba2aaf55236..429afff9b6642 100644 --- a/src/tools/clippy/clippy_lints/src/neg_multiply.rs +++ b/src/tools/clippy/clippy_lints/src/neg_multiply.rs @@ -2,7 +2,7 @@ use clippy_utils::consts::{self, Constant}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_context; use clippy_utils::sugg::has_enclosing_paren; -use rustc_ast::util::parser::PREC_PREFIX; +use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; @@ -58,7 +58,7 @@ fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) { { let mut applicability = Applicability::MachineApplicable; let (snip, from_macro) = snippet_with_context(cx, exp.span, span.ctxt(), "..", &mut applicability); - let suggestion = if !from_macro && exp.precedence() < PREC_PREFIX && !has_enclosing_paren(&snip) { + let suggestion = if !from_macro && exp.precedence() < ExprPrecedence::Prefix && !has_enclosing_paren(&snip) { format!("-({snip})") } else { format!("-{snip}") diff --git a/src/tools/clippy/clippy_lints/src/question_mark.rs b/src/tools/clippy/clippy_lints/src/question_mark.rs index 4b96858d8f66f..77abe7151f071 100644 --- a/src/tools/clippy/clippy_lints/src/question_mark.rs +++ b/src/tools/clippy/clippy_lints/src/question_mark.rs @@ -251,7 +251,7 @@ fn check_is_none_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Ex { let mut applicability = Applicability::MachineApplicable; let receiver_str = snippet_with_applicability(cx, caller.span, "..", &mut applicability); - let by_ref = !caller_ty.is_copy_modulo_regions(cx.tcx, cx.typing_env()) + let by_ref = !cx.type_is_copy_modulo_regions(caller_ty) && !matches!(caller.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)); let sugg = if let Some(else_inner) = r#else { if eq_expr_value(cx, caller, peel_blocks(else_inner)) { diff --git a/src/tools/clippy/clippy_lints/src/redundant_slicing.rs b/src/tools/clippy/clippy_lints/src/redundant_slicing.rs index 79baa914b0319..8e3472b1b5a1a 100644 --- a/src/tools/clippy/clippy_lints/src/redundant_slicing.rs +++ b/src/tools/clippy/clippy_lints/src/redundant_slicing.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::is_type_lang_item; use clippy_utils::{get_parent_expr, peel_middle_ty_refs}; -use rustc_ast::util::parser::PREC_PREFIX; +use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, LangItem, Mutability}; use rustc_lint::{LateContext, LateLintPass, Lint}; @@ -85,7 +85,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { let (expr_ty, expr_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(expr)); let (indexed_ty, indexed_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(indexed)); let parent_expr = get_parent_expr(cx, expr); - let needs_parens_for_prefix = parent_expr.is_some_and(|parent| parent.precedence() > PREC_PREFIX); + let needs_parens_for_prefix = parent_expr.is_some_and(|parent| parent.precedence() > ExprPrecedence::Prefix); if expr_ty == indexed_ty { if expr_ref_count > indexed_ref_count { diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs b/src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs index 8d71036084d3d..0d5cf45a5e653 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs @@ -1,7 +1,7 @@ use super::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::sugg::Sugg; -use rustc_ast::util::parser::AssocOp; +use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::Applicability; use rustc_hir::{Expr, Node}; use rustc_hir_typeck::cast::check_cast; @@ -44,8 +44,7 @@ pub(super) fn check<'tcx>( }; if let Node::Expr(parent) = cx.tcx.parent_hir_node(e.hir_id) - && parent.precedence() - > i8::try_from(AssocOp::As.precedence()).expect("AssocOp always returns a precedence < 128") + && parent.precedence() > ExprPrecedence::Cast { sugg = format!("({sugg})"); } diff --git a/src/tools/clippy/clippy_utils/src/ty.rs b/src/tools/clippy/clippy_utils/src/ty.rs index 39c9d234a1a16..260d1b801e3d9 100644 --- a/src/tools/clippy/clippy_utils/src/ty.rs +++ b/src/tools/clippy/clippy_utils/src/ty.rs @@ -38,7 +38,7 @@ pub use type_certainty::expr_type_is_certain; /// Checks if the given type implements copy. pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { - ty.is_copy_modulo_regions(cx.tcx, cx.typing_env()) + cx.type_is_copy_modulo_regions(ty) } /// This checks whether a given type is known to implement Debug. diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index e6fbba943a48c..36f876bcdc604 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeSet, HashMap, HashSet}; use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::process::Command; @@ -486,6 +486,9 @@ impl Config { } } +/// Known widths of `target_has_atomic`. +pub const KNOWN_TARGET_HAS_ATOMIC_WIDTHS: &[&str] = &["8", "16", "32", "64", "128", "ptr"]; + #[derive(Debug, Clone)] pub struct TargetCfgs { pub current: TargetCfg, @@ -611,6 +614,17 @@ impl TargetCfgs { ("panic", Some("abort")) => cfg.panic = PanicStrategy::Abort, ("panic", Some("unwind")) => cfg.panic = PanicStrategy::Unwind, ("panic", other) => panic!("unexpected value for panic cfg: {other:?}"), + + ("target_has_atomic", Some(width)) + if KNOWN_TARGET_HAS_ATOMIC_WIDTHS.contains(&width) => + { + cfg.target_has_atomic.insert(width.to_string()); + } + ("target_has_atomic", Some(other)) => { + panic!("unexpected value for `target_has_atomic` cfg: {other:?}") + } + // Nightly-only std-internal impl detail. + ("target_has_atomic", None) => {} _ => {} } } @@ -645,6 +659,12 @@ pub struct TargetCfg { pub(crate) xray: bool, #[serde(default = "default_reloc_model")] pub(crate) relocation_model: String, + + // Not present in target cfg json output, additional derived information. + #[serde(skip)] + /// Supported target atomic widths: e.g. `8` to `128` or `ptr`. This is derived from the builtin + /// `target_has_atomic` `cfg`s e.g. `target_has_atomic="8"`. + pub(crate) target_has_atomic: BTreeSet, } impl TargetCfg { diff --git a/src/tools/compiletest/src/directive-list.rs b/src/tools/compiletest/src/directive-list.rs index 952533e904c5a..5638d47189026 100644 --- a/src/tools/compiletest/src/directive-list.rs +++ b/src/tools/compiletest/src/directive-list.rs @@ -17,7 +17,6 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "check-run-results", "check-stdout", "check-test-line-numbers-match", - "compare-output-lines-by-subset", "compile-flags", "doc-flags", "dont-check-compiler-stderr", @@ -154,6 +153,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "needs-sanitizer-thread", "needs-std-debug-assertions", "needs-symlink", + "needs-target-has-atomic", "needs-threads", "needs-unwind", "needs-wasmtime", diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs index fe4c5fdd8b51c..73e56cfac8f68 100644 --- a/src/tools/compiletest/src/header.rs +++ b/src/tools/compiletest/src/header.rs @@ -115,9 +115,6 @@ pub struct TestProps { pub dont_check_compiler_stdout: bool, // For UI tests, allows compiler to generate arbitrary output to stderr pub dont_check_compiler_stderr: bool, - // When checking the output of stdout or stderr check - // that the lines of expected output are a subset of the actual output. - pub compare_output_lines_by_subset: bool, // Don't force a --crate-type=dylib flag on the command line // // Set this for example if you have an auxiliary test file that contains @@ -240,7 +237,6 @@ mod directives { pub const KNOWN_BUG: &'static str = "known-bug"; pub const TEST_MIR_PASS: &'static str = "test-mir-pass"; pub const REMAP_SRC_BASE: &'static str = "remap-src-base"; - pub const COMPARE_OUTPUT_LINES_BY_SUBSET: &'static str = "compare-output-lines-by-subset"; pub const LLVM_COV_FLAGS: &'static str = "llvm-cov-flags"; pub const FILECHECK_FLAGS: &'static str = "filecheck-flags"; pub const NO_AUTO_CHECK_CFG: &'static str = "no-auto-check-cfg"; @@ -274,7 +270,6 @@ impl TestProps { check_run_results: false, dont_check_compiler_stdout: false, dont_check_compiler_stderr: false, - compare_output_lines_by_subset: false, no_prefer_dynamic: false, pretty_mode: "normal".to_string(), pretty_compare_only: false, @@ -550,11 +545,6 @@ impl TestProps { |s| s.trim().to_string(), ); config.set_name_directive(ln, REMAP_SRC_BASE, &mut self.remap_src_base); - config.set_name_directive( - ln, - COMPARE_OUTPUT_LINES_BY_SUBSET, - &mut self.compare_output_lines_by_subset, - ); if let Some(flags) = config.parse_name_value_directive(ln, LLVM_COV_FLAGS) { self.llvm_cov_flags.extend(split_flags(&flags)); diff --git a/src/tools/compiletest/src/header/needs.rs b/src/tools/compiletest/src/header/needs.rs index 77570c58db5cf..e19dcd992fc2e 100644 --- a/src/tools/compiletest/src/header/needs.rs +++ b/src/tools/compiletest/src/header/needs.rs @@ -1,4 +1,4 @@ -use crate::common::{Config, Sanitizer}; +use crate::common::{Config, KNOWN_TARGET_HAS_ATOMIC_WIDTHS, Sanitizer}; use crate::header::{IgnoreDecision, llvm_has_libzstd}; pub(super) fn handle_needs( @@ -171,11 +171,54 @@ pub(super) fn handle_needs( }, ]; - let (name, comment) = match ln.split_once([':', ' ']) { - Some((name, comment)) => (name, Some(comment)), + let (name, rest) = match ln.split_once([':', ' ']) { + Some((name, rest)) => (name, Some(rest)), None => (ln, None), }; + // FIXME(jieyouxu): tighten up this parsing to reject using both `:` and ` ` as means to + // delineate value. + if name == "needs-target-has-atomic" { + let Some(rest) = rest else { + return IgnoreDecision::Error { + message: "expected `needs-target-has-atomic` to have a comma-separated list of atomic widths".to_string(), + }; + }; + + // Expect directive value to be a list of comma-separated atomic widths. + let specified_widths = rest + .split(',') + .map(|width| width.trim()) + .map(ToString::to_string) + .collect::>(); + + for width in &specified_widths { + if !KNOWN_TARGET_HAS_ATOMIC_WIDTHS.contains(&width.as_str()) { + return IgnoreDecision::Error { + message: format!( + "unknown width specified in `needs-target-has-atomic`: `{width}` is not a \ + known `target_has_atomic_width`, known values are `{:?}`", + KNOWN_TARGET_HAS_ATOMIC_WIDTHS + ), + }; + } + } + + let satisfies_all_specified_widths = specified_widths + .iter() + .all(|specified| config.target_cfg().target_has_atomic.contains(specified)); + if satisfies_all_specified_widths { + return IgnoreDecision::Continue; + } else { + return IgnoreDecision::Ignore { + reason: format!( + "skipping test as target does not support all of the required `target_has_atomic` widths `{:?}`", + specified_widths + ), + }; + } + } + if !name.starts_with("needs-") { return IgnoreDecision::Continue; } @@ -193,7 +236,7 @@ pub(super) fn handle_needs( break; } else { return IgnoreDecision::Ignore { - reason: if let Some(comment) = comment { + reason: if let Some(comment) = rest { format!("{} ({})", need.ignore_reason, comment.trim()) } else { need.ignore_reason.into() diff --git a/src/tools/compiletest/src/header/tests.rs b/src/tools/compiletest/src/header/tests.rs index 4d75c38dd3206..cd7c6f8361ed3 100644 --- a/src/tools/compiletest/src/header/tests.rs +++ b/src/tools/compiletest/src/header/tests.rs @@ -787,3 +787,40 @@ fn test_not_trailing_directive() { run_path(&mut poisoned, Path::new("a.rs"), b"//@ revisions: incremental"); assert!(!poisoned); } + +#[test] +fn test_needs_target_has_atomic() { + use std::collections::BTreeSet; + + // `x86_64-unknown-linux-gnu` supports `["8", "16", "32", "64", "ptr"]` but not `128`. + + let config = cfg().target("x86_64-unknown-linux-gnu").build(); + // Expectation sanity check. + assert_eq!( + config.target_cfg().target_has_atomic, + BTreeSet::from([ + "8".to_string(), + "16".to_string(), + "32".to_string(), + "64".to_string(), + "ptr".to_string() + ]), + "expected `x86_64-unknown-linux-gnu` to not have 128-bit atomic support" + ); + + assert!(!check_ignore(&config, "//@ needs-target-has-atomic: 8")); + assert!(!check_ignore(&config, "//@ needs-target-has-atomic: 16")); + assert!(!check_ignore(&config, "//@ needs-target-has-atomic: 32")); + assert!(!check_ignore(&config, "//@ needs-target-has-atomic: 64")); + assert!(!check_ignore(&config, "//@ needs-target-has-atomic: ptr")); + + assert!(check_ignore(&config, "//@ needs-target-has-atomic: 128")); + + assert!(!check_ignore(&config, "//@ needs-target-has-atomic: 8,16,32,64,ptr")); + + assert!(check_ignore(&config, "//@ needs-target-has-atomic: 8,16,32,64,ptr,128")); + + // Check whitespace between widths is permitted. + assert!(!check_ignore(&config, "//@ needs-target-has-atomic: 8, ptr")); + assert!(check_ignore(&config, "//@ needs-target-has-atomic: 8, ptr, 128")); +} diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 93e5980f1f5fb..7be9e2f2d5772 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -2541,13 +2541,10 @@ impl<'test> TestCx<'test> { return 0; } - // If `compare-output-lines-by-subset` is not explicitly enabled then - // auto-enable it when a `runner` is in use since wrapper tools might - // provide extra output on failure, for example a WebAssembly runtime - // might print the stack trace of an `unreachable` instruction by - // default. - let compare_output_by_lines = - self.props.compare_output_lines_by_subset || self.config.runner.is_some(); + // Wrapper tools set by `runner` might provide extra output on failure, + // for example a WebAssembly runtime might print the stack trace of an + // `unreachable` instruction by default. + let compare_output_by_lines = self.config.runner.is_some(); let tmp; let (expected, actual): (&str, &str) = if compare_output_by_lines { diff --git a/tests/rustdoc-json/impls/trait-for-dyn-trait.rs b/tests/rustdoc-json/impls/trait-for-dyn-trait.rs new file mode 100644 index 0000000000000..0fbb4df002847 --- /dev/null +++ b/tests/rustdoc-json/impls/trait-for-dyn-trait.rs @@ -0,0 +1,15 @@ +//@ set t1 = '$.index[*][?(@.name=="T1")].id' +pub trait T1 {} + +//@ set t2 = '$.index[*][?(@.name=="T2")].id' +pub trait T2 {} + +/// Fun impl +impl T1 for dyn T2 {} + +//@ set impl = '$.index[*][?(@.docs=="Fun impl")].id' +//@ is '$.index[*][?(@.name=="T1")].inner.trait.implementations[*]' $impl +//@ is '$.index[*][?(@.name=="T2")].inner.trait.implementations' [] + +//@ is '$.index[*][?(@.docs=="Fun impl")].inner.impl.trait.id' $t1 +//@ is '$.index[*][?(@.docs=="Fun impl")].inner.impl.for.dyn_trait.traits[*].trait.id' $t2 diff --git a/tests/rustdoc-json/statics/extern.rs b/tests/rustdoc-json/statics/extern.rs new file mode 100644 index 0000000000000..d38fdf1cd1cdd --- /dev/null +++ b/tests/rustdoc-json/statics/extern.rs @@ -0,0 +1,39 @@ +// ignore-tidy-linelength +//@ edition: 2021 + +extern "C" { + //@ is '$.index[*][?(@.name=="A")].inner.static.is_unsafe' true + //@ is '$.index[*][?(@.name=="A")].inner.static.is_mutable' false + pub static A: i32; + //@ is '$.index[*][?(@.name=="B")].inner.static.is_unsafe' true + //@ is '$.index[*][?(@.name=="B")].inner.static.is_mutable' true + pub static mut B: i32; + + // items in unadorned `extern` blocks cannot have safety qualifiers +} + +unsafe extern "C" { + //@ is '$.index[*][?(@.name=="C")].inner.static.is_unsafe' true + //@ is '$.index[*][?(@.name=="C")].inner.static.is_mutable' false + pub static C: i32; + //@ is '$.index[*][?(@.name=="D")].inner.static.is_unsafe' true + //@ is '$.index[*][?(@.name=="D")].inner.static.is_mutable' true + pub static mut D: i32; + + //@ is '$.index[*][?(@.name=="E")].inner.static.is_unsafe' false + //@ is '$.index[*][?(@.name=="E")].inner.static.is_mutable' false + pub safe static E: i32; + //@ is '$.index[*][?(@.name=="F")].inner.static.is_unsafe' false + //@ is '$.index[*][?(@.name=="F")].inner.static.is_mutable' true + pub safe static mut F: i32; + + //@ is '$.index[*][?(@.name=="G")].inner.static.is_unsafe' true + //@ is '$.index[*][?(@.name=="G")].inner.static.is_mutable' false + pub unsafe static G: i32; + //@ is '$.index[*][?(@.name=="H")].inner.static.is_unsafe' true + //@ is '$.index[*][?(@.name=="H")].inner.static.is_mutable' true + pub unsafe static mut H: i32; +} + +//@ ismany '$.index[*][?(@.inner.static)].inner.static.expr' '""' '""' '""' '""' '""' '""' '""' '""' +//@ ismany '$.index[*][?(@.inner.static)].inner.static.type.primitive' '"i32"' '"i32"' '"i32"' '"i32"' '"i32"' '"i32"' '"i32"' '"i32"' diff --git a/tests/rustdoc-json/statics/statics.rs b/tests/rustdoc-json/statics/statics.rs new file mode 100644 index 0000000000000..a8af23cc87dcc --- /dev/null +++ b/tests/rustdoc-json/statics/statics.rs @@ -0,0 +1,12 @@ +//@ is '$.index[*][?(@.name=="A")].inner.static.type.primitive' '"i32"' +//@ is '$.index[*][?(@.name=="A")].inner.static.is_mutable' false +//@ is '$.index[*][?(@.name=="A")].inner.static.expr' '"5"' +//@ is '$.index[*][?(@.name=="A")].inner.static.is_unsafe' false +pub static A: i32 = 5; + +//@ is '$.index[*][?(@.name=="B")].inner.static.type.primitive' '"u32"' +//@ is '$.index[*][?(@.name=="B")].inner.static.is_mutable' true +// Expr value isn't gaurenteed, it'd be fine to change it. +//@ is '$.index[*][?(@.name=="B")].inner.static.expr' '"_"' +//@ is '$.index[*][?(@.name=="B")].inner.static.is_unsafe' false +pub static mut B: u32 = 2 + 3; diff --git a/tests/ui-fulldeps/pprust-parenthesis-insertion.rs b/tests/ui-fulldeps/pprust-parenthesis-insertion.rs new file mode 100644 index 0000000000000..fd6644d73c161 --- /dev/null +++ b/tests/ui-fulldeps/pprust-parenthesis-insertion.rs @@ -0,0 +1,242 @@ +//@ run-pass +//@ ignore-cross-compile + +// This test covers the AST pretty-printer's automatic insertion of parentheses +// into unparenthesized syntax trees according to precedence and various grammar +// restrictions and edge cases. +// +// For example if the following syntax tree represents the expression a*(b+c), +// in which the parenthesis is necessary for precedence: +// +// Binary('*', Path("a"), Paren(Binary('+', Path("b"), Path("c")))) +// +// then the pretty-printer needs to be able to print the following +// unparenthesized syntax tree with an automatically inserted parenthesization. +// +// Binary('*', Path("a"), Binary('+', Path("b"), Path("c"))) +// +// Handling this correctly is relevant in real-world code when pretty-printing +// macro-generated syntax trees, in which expressions can get interpolated into +// one another without any parenthesization being visible in the syntax tree. +// +// macro_rules! repro { +// ($rhs:expr) => { +// a * $rhs +// }; +// } +// +// let _ = repro!(b + c); + +#![feature(rustc_private)] + +extern crate rustc_ast; +extern crate rustc_ast_pretty; +extern crate rustc_driver; +extern crate rustc_errors; +extern crate rustc_parse; +extern crate rustc_session; +extern crate rustc_span; +extern crate smallvec; + +use std::mem; +use std::process::ExitCode; + +use rustc_ast::ast::{DUMMY_NODE_ID, Expr, ExprKind, Stmt}; +use rustc_ast::mut_visit::{self, DummyAstNode as _, MutVisitor}; +use rustc_ast::node_id::NodeId; +use rustc_ast::ptr::P; +use rustc_ast_pretty::pprust; +use rustc_errors::Diag; +use rustc_parse::parser::Recovery; +use rustc_session::parse::ParseSess; +use rustc_span::{DUMMY_SP, FileName, Span}; +use smallvec::SmallVec; + +// Every parenthesis in the following expressions is re-inserted by the +// pretty-printer. +// +// FIXME: Some of them shouldn't be. +static EXPRS: &[&str] = &[ + // Straightforward binary operator precedence. + "2 * 2 + 2", + "2 + 2 * 2", + "(2 + 2) * 2", + "2 * (2 + 2)", + "2 + 2 + 2", + // Return has lower precedence than a binary operator. + "(return 2) + 2", + "2 + (return 2)", // FIXME: no parenthesis needed. + "(return) + 2", // FIXME: no parenthesis needed. + // These mean different things. + "return - 2", + "(return) - 2", + // These mean different things. + "if let _ = true && false {}", + "if let _ = (true && false) {}", + // Conditions end at the first curly brace, so struct expressions need to be + // parenthesized. Except in a match guard, where conditions end at arrow. + "if let _ = (Struct {}) {}", + "match 2 { _ if let _ = Struct {} => {} }", + // Match arms terminate eagerly, so parenthesization is needed around some + // expressions. + "match 2 { _ => 1 - 1 }", + "match 2 { _ => ({ 1 }) - 1 }", + // Grammar restriction: break value starting with a labeled loop is not + // allowed, except if the break is also labeled. + "break 'outer 'inner: loop {} + 2", + "break ('inner: loop {} + 2)", + // Grammar restriction: the value in let-else is not allowed to end in a + // curly brace. + "{ let _ = 1 + 1 else {}; }", + "{ let _ = (loop {}) else {}; }", + "{ let _ = mac!() else {}; }", + "{ let _ = (mac! {}) else {}; }", + // Parentheses are necessary to prevent an eager statement boundary. + "{ 2 - 1 }", + "{ (match 2 {}) - 1 }", + "{ (match 2 {})() - 1 }", + "{ (match 2 {})[0] - 1 }", + "{ (loop {}) - 1 }", + // Angle bracket is eagerly parsed as a path's generic argument list. + "(2 as T) < U", + "(2 as T) < V", // FIXME: no parentheses needed. + /* + // FIXME: pretty-printer produces invalid syntax. `2 + 2 as T < U` + "(2 + 2 as T) < U", + */ + /* + // FIXME: pretty-printer produces invalid syntax. `if (let _ = () && Struct {}.x) {}` + "if let _ = () && (Struct {}).x {}", + */ + /* + // FIXME: pretty-printer produces invalid syntax. `(1 < 2 == false) as usize` + "((1 < 2) == false) as usize", + */ + /* + // FIXME: pretty-printer produces invalid syntax. `for _ in 1..{ 2 } {}` + "for _ in (1..{ 2 }) {}", + */ + /* + // FIXME: pretty-printer loses the attribute. `{ let Struct { field } = s; }` + "{ let Struct { #[attr] field } = s; }", + */ + /* + // FIXME: pretty-printer turns this into a range. `0..to_string()` + "(0.).to_string()", + "0. .. 1.", + */ + /* + // FIXME: pretty-printer loses the dyn*. `i as Trait` + "i as dyn* Trait", + */ +]; + +// Flatten the content of parenthesis nodes into their parent node. For example +// this syntax tree representing the expression a*(b+c): +// +// Binary('*', Path("a"), Paren(Binary('+', Path("b"), Path("c")))) +// +// would unparenthesize to: +// +// Binary('*', Path("a"), Binary('+', Path("b"), Path("c"))) +struct Unparenthesize; + +impl MutVisitor for Unparenthesize { + fn visit_expr(&mut self, e: &mut P) { + while let ExprKind::Paren(paren) = &mut e.kind { + **e = mem::replace(&mut *paren, Expr::dummy()); + } + mut_visit::walk_expr(self, e); + } +} + +// Erase Span information that could distinguish between identical expressions +// parsed from different source strings. +struct Normalize; + +impl MutVisitor for Normalize { + const VISIT_TOKENS: bool = true; + + fn visit_id(&mut self, id: &mut NodeId) { + *id = DUMMY_NODE_ID; + } + + fn visit_span(&mut self, span: &mut Span) { + *span = DUMMY_SP; + } + + fn visit_expr(&mut self, expr: &mut P) { + if let ExprKind::Binary(binop, _left, _right) = &mut expr.kind { + self.visit_span(&mut binop.span); + } + mut_visit::walk_expr(self, expr); + } + + fn flat_map_stmt(&mut self, mut stmt: Stmt) -> SmallVec<[Stmt; 1]> { + self.visit_span(&mut stmt.span); + mut_visit::walk_flat_map_stmt(self, stmt) + } +} + +fn parse_expr(psess: &ParseSess, source_code: &str) -> Option> { + let parser = rustc_parse::unwrap_or_emit_fatal(rustc_parse::new_parser_from_source_str( + psess, + FileName::anon_source_code(source_code), + source_code.to_owned(), + )); + + let mut expr = parser.recovery(Recovery::Forbidden).parse_expr().map_err(Diag::cancel).ok()?; + Normalize.visit_expr(&mut expr); + Some(expr) +} + +fn main() -> ExitCode { + let mut status = ExitCode::SUCCESS; + let mut fail = |description: &str, before: &str, after: &str| { + status = ExitCode::FAILURE; + eprint!( + "{description}\n BEFORE: {before}\n AFTER: {after}\n\n", + before = before.replace('\n', "\n "), + after = after.replace('\n', "\n "), + ); + }; + + rustc_span::create_default_session_globals_then(|| { + let psess = &ParseSess::new(vec![rustc_parse::DEFAULT_LOCALE_RESOURCE]); + + for &source_code in EXPRS { + let expr = parse_expr(psess, source_code).unwrap(); + + // Check for FALSE POSITIVE: pretty-printer inserting parentheses where not needed. + // Pseudocode: + // assert(expr == parse(print(expr))) + let printed = &pprust::expr_to_string(&expr); + let Some(expr2) = parse_expr(psess, printed) else { + fail("Pretty-printer produced invalid syntax", source_code, printed); + continue; + }; + if format!("{expr:#?}") != format!("{expr2:#?}") { + fail("Pretty-printer inserted unnecessary parenthesis", source_code, printed); + continue; + } + + // Check for FALSE NEGATIVE: pretty-printer failing to place necessary parentheses. + // Pseudocode: + // assert(unparenthesize(expr) == unparenthesize(parse(print(unparenthesize(expr))))) + let mut expr = expr; + Unparenthesize.visit_expr(&mut expr); + let printed = &pprust::expr_to_string(&expr); + let Some(mut expr2) = parse_expr(psess, printed) else { + fail("Pretty-printer with no parens produced invalid syntax", source_code, printed); + continue; + }; + Unparenthesize.visit_expr(&mut expr2); + if format!("{expr:#?}") != format!("{expr2:#?}") { + fail("Pretty-printer lost necessary parentheses", source_code, printed); + continue; + } + } + }); + + status +} diff --git a/tests/ui/README.md b/tests/ui/README.md index c14d0ee78c8df..aa36481ae06eb 100644 --- a/tests/ui/README.md +++ b/tests/ui/README.md @@ -6,9 +6,9 @@ This folder contains `rustc`'s ## Test Directives (Headers) Typically, a UI test will have some test directives / headers which are -special comments that tell compiletest how to build and intepret a test. +special comments that tell compiletest how to build and interpret a test. -As part of an on-going effort to rewrite compiletest +As part of an ongoing effort to rewrite compiletest (see ), a major change proposal to change legacy compiletest-style headers `// ` to [`ui_test`](https://github.com/oli-obk/ui_test)-style headers @@ -30,6 +30,6 @@ but in `ui_test` style, the header would be written as compiletest is changed to accept only `//@` directives for UI tests (currently), and will reject and report an error if it encounters any -comments `// ` that may be parsed as an legacy compiletest-style +comments `// ` that may be parsed as a legacy compiletest-style test header. To fix this, you should migrate to the `ui_test`-style header `//@ `. diff --git a/triagebot.toml b/triagebot.toml index 47b968240984d..4d6806711fbc8 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -996,7 +996,6 @@ warn_non_default_branch = true contributing_url = "https://rustc-dev-guide.rust-lang.org/getting-started.html" users_on_vacation = [ "jyn514", - "oli-obk", ] [assign.adhoc_groups]