Merge from rustc

This commit is contained in:
The Miri Cronjob Bot 2025-04-07 05:24:10 +00:00
commit 2eb7791a1b
994 changed files with 9961 additions and 8374 deletions

View file

@ -276,7 +276,7 @@ Jacob Greenfield <xales@naveria.com>
Jacob Pratt <jacob@jhpratt.dev> <the.z.cuber@gmail.com>
Jacob Pratt <jacob@jhpratt.dev> <jacopratt@tesla.com>
Jake Goulding <jake.goulding@integer32.com>
Jake Goulding <jake.goulding@integer32.com> <jake.goulding@gmail.com>
Jake Goulding <jake.goulding@integer32.com> <jake.goulding@gmail.com>
Jake Goulding <jake.goulding@integer32.com> <shepmaster@mac.com>
Jake Vossen <jake@vossen.dev>
Jakob Degen <jakob.e.degen@gmail.com> <jakob@degen.com>
@ -408,10 +408,13 @@ Luqman Aden <me@luqman.ca> <laden@mozilla.com>
Luqman Aden <me@luqman.ca> <rust@luqman.ca>
Lzu Tao <taolzu@gmail.com>
Maik Klein <maikklein@googlemail.com>
Maja Kądziołka <maya@compilercrim.es> <github@compilercrim.es>
Maja Kądziołka <maya@compilercrim.es> <kuba@kadziolka.net>
Malo Jaffré <jaffre.malo@gmail.com>
Manish Goregaokar <manishsmail@gmail.com>
Mara Bos <m-ou.se@m-ou.se>
Marcell Pardavi <marcell.pardavi@gmail.com>
Marco Ieni <11428655+MarcoIeni@users.noreply.github.com>
Marcus Klaas de Vries <mail@marcusklaas.nl>
Margaret Meyerhofer <mmeyerho@andrew.cmu.edu> <mmeyerho@andrew>
Mark Mansi <markm@cs.wisc.edu>
@ -565,6 +568,9 @@ Robert Habermeier <rphmeier@gmail.com>
Robert Millar <robert.millar@cantab.net>
Roc Yu <rocyu@protonmail.com>
Rohit Joshi <rohitjoshi@users.noreply.github.com> Rohit Joshi <rohit.joshi@capitalone.com>
Ross Smyth <18294397+RossSmyth@users.noreply.github.com>
Ross Smyth <18294397+RossSmyth@users.noreply.github.com> <crs2017@gmail.com>
Ross Smyth <18294397+RossSmyth@users.noreply.github.com> <rsmyth@electrocraft.com>
Roxane Fruytier <roxane.fruytier@hotmail.com>
Rui <xiongmao86dev@sina.com>
Russell Johnston <rpjohnst@gmail.com>

View file

@ -239,9 +239,9 @@ checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "blake3"
version = "1.8.0"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34a796731680be7931955498a16a10b2270c7762963d5d570fdbfe02dcbf314f"
checksum = "389a099b34312839e16420d499a9cad9650541715937ffbdd40d36f49e77eeb3"
dependencies = [
"arrayref",
"arrayvec",
@ -800,9 +800,9 @@ dependencies = [
[[package]]
name = "ctrlc"
version = "3.4.5"
version = "3.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
checksum = "697b5419f348fd5ae2478e8018cb016c00a5881c7f46c717de98ffd135a5651c"
dependencies = [
"nix",
"windows-sys 0.59.0",
@ -1079,9 +1079,9 @@ dependencies = [
[[package]]
name = "env_logger"
version = "0.11.7"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697"
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
dependencies = [
"anstream",
"anstyle",
@ -1098,9 +1098,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.10"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
dependencies = [
"libc",
"windows-sys 0.59.0",
@ -1163,12 +1163,12 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.1.0"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
dependencies = [
"crc32fast",
"miniz_oxide 0.8.5",
"miniz_oxide 0.8.7",
]
[[package]]
@ -1773,9 +1773,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "indexmap"
version = "2.8.0"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [
"equivalent",
"hashbrown",
@ -2252,9 +2252,9 @@ dependencies = [
[[package]]
name = "miniz_oxide"
version = "0.8.5"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430"
dependencies = [
"adler2",
]
@ -2506,9 +2506,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-sys"
version = "0.9.106"
version = "0.9.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd"
checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07"
dependencies = [
"cc",
"libc",
@ -2970,9 +2970,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.5.10"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
dependencies = [
"bitflags",
]
@ -3150,6 +3150,12 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustc-literal-escaper"
version = "0.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0041b6238913c41fe704213a4a9329e2f685a156d1781998128b4149c230ad04"
[[package]]
name = "rustc-main"
version = "0.0.0"
@ -3242,10 +3248,10 @@ version = "0.0.0"
dependencies = [
"bitflags",
"memchr",
"rustc-literal-escaper",
"rustc_ast_ir",
"rustc_data_structures",
"rustc_index",
"rustc_lexer",
"rustc_macros",
"rustc_serialize",
"rustc_span",
@ -4200,6 +4206,7 @@ name = "rustc_parse"
version = "0.0.0"
dependencies = [
"bitflags",
"rustc-literal-escaper",
"rustc_ast",
"rustc_ast_pretty",
"rustc_data_structures",
@ -4222,6 +4229,7 @@ dependencies = [
name = "rustc_parse_format"
version = "0.0.0"
dependencies = [
"rustc-literal-escaper",
"rustc_index",
"rustc_lexer",
]
@ -4432,7 +4440,7 @@ dependencies = [
"rustc_span",
"rustc_target",
"scoped-tls",
"stable_mir",
"serde",
"tracing",
]
@ -4918,9 +4926,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.14.0"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
[[package]]
name = "socket2"
@ -4990,8 +4998,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
name = "stable_mir"
version = "0.1.0-preview"
dependencies = [
"scoped-tls",
"serde",
"rustc_smir",
]
[[package]]
@ -5369,9 +5376,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.44.1"
version = "1.44.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
dependencies = [
"backtrace",
"bytes",

View file

@ -7,10 +7,10 @@ edition = "2024"
# tidy-alphabetical-start
bitflags = "2.4.1"
memchr = "2.7.4"
rustc-literal-escaper = "0.0.2"
rustc_ast_ir = { path = "../rustc_ast_ir" }
rustc_data_structures = { path = "../rustc_data_structures" }
rustc_index = { path = "../rustc_index" }
rustc_lexer = { path = "../rustc_lexer" }
rustc_macros = { path = "../rustc_macros" }
rustc_serialize = { path = "../rustc_serialize" }
rustc_span = { path = "../rustc_span" }

View file

@ -981,6 +981,75 @@ impl BinOpKind {
pub type BinOp = Spanned<BinOpKind>;
// Sometimes `BinOpKind` and `AssignOpKind` need the same treatment. The
// operations covered by `AssignOpKind` are a subset of those covered by
// `BinOpKind`, so it makes sense to convert `AssignOpKind` to `BinOpKind`.
impl From<AssignOpKind> for BinOpKind {
fn from(op: AssignOpKind) -> BinOpKind {
match op {
AssignOpKind::AddAssign => BinOpKind::Add,
AssignOpKind::SubAssign => BinOpKind::Sub,
AssignOpKind::MulAssign => BinOpKind::Mul,
AssignOpKind::DivAssign => BinOpKind::Div,
AssignOpKind::RemAssign => BinOpKind::Rem,
AssignOpKind::BitXorAssign => BinOpKind::BitXor,
AssignOpKind::BitAndAssign => BinOpKind::BitAnd,
AssignOpKind::BitOrAssign => BinOpKind::BitOr,
AssignOpKind::ShlAssign => BinOpKind::Shl,
AssignOpKind::ShrAssign => BinOpKind::Shr,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Encodable, Decodable, HashStable_Generic)]
pub enum AssignOpKind {
/// The `+=` operator (addition)
AddAssign,
/// The `-=` operator (subtraction)
SubAssign,
/// The `*=` operator (multiplication)
MulAssign,
/// The `/=` operator (division)
DivAssign,
/// The `%=` operator (modulus)
RemAssign,
/// The `^=` operator (bitwise xor)
BitXorAssign,
/// The `&=` operator (bitwise and)
BitAndAssign,
/// The `|=` operator (bitwise or)
BitOrAssign,
/// The `<<=` operator (shift left)
ShlAssign,
/// The `>>=` operator (shift right)
ShrAssign,
}
impl AssignOpKind {
pub fn as_str(&self) -> &'static str {
use AssignOpKind::*;
match self {
AddAssign => "+=",
SubAssign => "-=",
MulAssign => "*=",
DivAssign => "/=",
RemAssign => "%=",
BitXorAssign => "^=",
BitAndAssign => "&=",
BitOrAssign => "|=",
ShlAssign => "<<=",
ShrAssign => ">>=",
}
}
/// AssignOps are always by value.
pub fn is_by_value(self) -> bool {
true
}
}
pub type AssignOp = Spanned<AssignOpKind>;
/// Unary operator.
///
/// Note that `&data` is not an operator, it's an `AddrOf` expression.
@ -1593,7 +1662,7 @@ pub enum ExprKind {
/// An assignment with an operator.
///
/// E.g., `a += 1`.
AssignOp(BinOp, P<Expr>, P<Expr>),
AssignOp(AssignOp, P<Expr>, P<Expr>),
/// Access of a named (e.g., `obj.foo`) or unnamed (e.g., `obj.0`) struct field.
Field(P<Expr>, Ident),
/// An indexing operation (e.g., `foo[2]`).

View file

@ -570,6 +570,14 @@ impl MetaItemInner {
}
}
/// Returns the bool if `self` is a boolean `MetaItemInner::Literal`.
pub fn boolean_literal(&self) -> Option<bool> {
match self {
MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => Some(*b),
_ => None,
}
}
/// Returns the `MetaItem` if `self` is a `MetaItemInner::MetaItem` or if it's
/// `MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(_), .. })`.
pub fn meta_item_or_bool(&self) -> Option<&MetaItemInner> {

View file

@ -77,6 +77,17 @@ pub struct AutoDiffAttrs {
/// e.g. in the [JAX
/// Documentation](https://jax.readthedocs.io/en/latest/_tutorials/advanced-autodiff.html#how-it-s-made-two-foundational-autodiff-functions).
pub mode: DiffMode,
/// A user-provided, batching width. If not given, we will default to 1 (no batching).
/// Calling a differentiated, non-batched function through a loop 100 times is equivalent to:
/// - Calling the function 50 times with a batch size of 2
/// - Calling the function 25 times with a batch size of 4,
/// etc. A batched function takes more (or longer) arguments, and might be able to benefit from
/// cache locality, better re-usal of primal values, and other optimizations.
/// We will (before LLVM's vectorizer runs) just generate most LLVM-IR instructions `width`
/// times, so this massively increases code size. As such, values like 1024 are unlikely to
/// work. We should consider limiting this to u8 or u16, but will leave it at u32 for
/// experiments for now and focus on documenting the implications of a large width.
pub width: u32,
pub ret_activity: DiffActivity,
pub input_activity: Vec<DiffActivity>,
}
@ -222,6 +233,7 @@ impl AutoDiffAttrs {
pub const fn error() -> Self {
AutoDiffAttrs {
mode: DiffMode::Error,
width: 0,
ret_activity: DiffActivity::None,
input_activity: Vec::new(),
}
@ -229,6 +241,7 @@ impl AutoDiffAttrs {
pub fn source() -> Self {
AutoDiffAttrs {
mode: DiffMode::Source,
width: 0,
ret_activity: DiffActivity::None,
input_activity: Vec::new(),
}

View file

@ -2,7 +2,7 @@
use std::{ascii, fmt, str};
use rustc_lexer::unescape::{
use rustc_literal_escaper::{
MixedUnit, Mode, byte_from_char, unescape_byte, unescape_char, unescape_mixed, unescape_unicode,
};
use rustc_span::{Span, Symbol, kw, sym};

View file

@ -1,6 +1,6 @@
use rustc_span::kw;
use crate::ast::{self, BinOpKind, RangeLimits};
use crate::ast::{self, AssignOpKind, BinOpKind, RangeLimits};
use crate::token::{self, Token};
/// Associative operator.
@ -9,7 +9,7 @@ pub enum AssocOp {
/// A binary op.
Binary(BinOpKind),
/// `?=` where ? is one of the assignable BinOps
AssignOp(BinOpKind),
AssignOp(AssignOpKind),
/// `=`
Assign,
/// `as`
@ -44,16 +44,16 @@ impl AssocOp {
token::Or => Some(Binary(BinOpKind::BitOr)),
token::Shl => Some(Binary(BinOpKind::Shl)),
token::Shr => Some(Binary(BinOpKind::Shr)),
token::PlusEq => Some(AssignOp(BinOpKind::Add)),
token::MinusEq => Some(AssignOp(BinOpKind::Sub)),
token::StarEq => Some(AssignOp(BinOpKind::Mul)),
token::SlashEq => Some(AssignOp(BinOpKind::Div)),
token::PercentEq => Some(AssignOp(BinOpKind::Rem)),
token::CaretEq => Some(AssignOp(BinOpKind::BitXor)),
token::AndEq => Some(AssignOp(BinOpKind::BitAnd)),
token::OrEq => Some(AssignOp(BinOpKind::BitOr)),
token::ShlEq => Some(AssignOp(BinOpKind::Shl)),
token::ShrEq => Some(AssignOp(BinOpKind::Shr)),
token::PlusEq => Some(AssignOp(AssignOpKind::AddAssign)),
token::MinusEq => Some(AssignOp(AssignOpKind::SubAssign)),
token::StarEq => Some(AssignOp(AssignOpKind::MulAssign)),
token::SlashEq => Some(AssignOp(AssignOpKind::DivAssign)),
token::PercentEq => Some(AssignOp(AssignOpKind::RemAssign)),
token::CaretEq => Some(AssignOp(AssignOpKind::BitXorAssign)),
token::AndEq => Some(AssignOp(AssignOpKind::BitAndAssign)),
token::OrEq => Some(AssignOp(AssignOpKind::BitOrAssign)),
token::ShlEq => Some(AssignOp(AssignOpKind::ShlAssign)),
token::ShrEq => Some(AssignOp(AssignOpKind::ShrAssign)),
token::Lt => Some(Binary(BinOpKind::Lt)),
token::Le => Some(Binary(BinOpKind::Le)),
token::Ge => Some(Binary(BinOpKind::Ge)),

View file

@ -74,14 +74,16 @@ impl<'hir> LoweringContext<'_, 'hir> {
// Merge attributes into the inner expression.
if !e.attrs.is_empty() {
let old_attrs = self.attrs.get(&ex.hir_id.local_id).copied().unwrap_or(&[]);
self.attrs.insert(
ex.hir_id.local_id,
&*self.arena.alloc_from_iter(
self.lower_attrs_vec(&e.attrs, e.span)
.into_iter()
.chain(old_attrs.iter().cloned()),
),
let attrs = &*self.arena.alloc_from_iter(
self.lower_attrs_vec(&e.attrs, e.span)
.into_iter()
.chain(old_attrs.iter().cloned()),
);
if attrs.is_empty() {
return ex;
}
self.attrs.insert(ex.hir_id.local_id, attrs);
}
return ex;
}
@ -274,7 +276,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
}
ExprKind::Assign(el, er, span) => self.lower_expr_assign(el, er, *span, e.span),
ExprKind::AssignOp(op, el, er) => hir::ExprKind::AssignOp(
self.lower_binop(*op),
self.lower_assign_op(*op),
self.lower_expr(el),
self.lower_expr(er),
),
@ -443,6 +445,10 @@ impl<'hir> LoweringContext<'_, 'hir> {
Spanned { node: b.node, span: self.lower_span(b.span) }
}
fn lower_assign_op(&mut self, a: AssignOp) -> AssignOp {
Spanned { node: a.node, span: self.lower_span(a.span) }
}
fn lower_legacy_const_generics(
&mut self,
mut f: Expr,

View file

@ -676,12 +676,9 @@ impl<'hir> LoweringContext<'_, 'hir> {
let ty =
self.lower_ty(ty, ImplTraitContext::Disallowed(ImplTraitPosition::StaticTy));
let safety = self.lower_safety(*safety, hir::Safety::Unsafe);
// njn: where for this?
if define_opaque.is_some() {
self.dcx().span_err(i.span, "foreign statics cannot define opaque types");
}
(ident, hir::ForeignItemKind::Static(ty, *mutability, safety))
}
ForeignItemKind::TyAlias(box TyAlias { ident, .. }) => {

View file

@ -332,17 +332,19 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
ast::ExprKind::TryBlock(_) => {
gate!(&self, try_blocks, e.span, "`try` expression is experimental");
}
ast::ExprKind::Lit(token::Lit { kind: token::LitKind::Float, suffix, .. }) => {
match suffix {
Some(sym::f16) => {
gate!(&self, f16, e.span, "the type `f16` is unstable")
}
Some(sym::f128) => {
gate!(&self, f128, e.span, "the type `f128` is unstable")
}
_ => (),
ast::ExprKind::Lit(token::Lit {
kind: token::LitKind::Float | token::LitKind::Integer,
suffix,
..
}) => match suffix {
Some(sym::f16) => {
gate!(&self, f16, e.span, "the type `f16` is unstable")
}
}
Some(sym::f128) => {
gate!(&self, f128, e.span, "the type `f128` is unstable")
}
_ => (),
},
_ => {}
}
visit::walk_expr(self, e)
@ -511,6 +513,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
gate_all!(contracts, "contracts are incomplete");
gate_all!(contracts_internals, "contract internal machinery is for internal use only");
gate_all!(where_clause_attrs, "attributes in `where` clause are unstable");
gate_all!(super_let, "`super let` is experimental");
if !visitor.features.never_patterns() {
if let Some(spans) = spans.get(&sym::never_patterns) {

View file

@ -274,22 +274,22 @@ impl<'a> State<'a> {
fn print_expr_binary(
&mut self,
op: ast::BinOp,
op: ast::BinOpKind,
lhs: &ast::Expr,
rhs: &ast::Expr,
fixup: FixupContext,
) {
let binop_prec = op.node.precedence();
let binop_prec = op.precedence();
let left_prec = lhs.precedence();
let right_prec = rhs.precedence();
let (mut left_needs_paren, right_needs_paren) = match op.node.fixity() {
let (mut left_needs_paren, right_needs_paren) = match 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),
};
match (&lhs.kind, op.node) {
match (&lhs.kind, op) {
// 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.
@ -312,7 +312,7 @@ impl<'a> State<'a> {
self.print_expr_cond_paren(lhs, left_needs_paren, fixup.leftmost_subexpression());
self.space();
self.word_space(op.node.as_str());
self.word_space(op.as_str());
self.print_expr_cond_paren(rhs, right_needs_paren, fixup.subsequent_subexpression());
}
@ -410,7 +410,7 @@ impl<'a> State<'a> {
self.print_expr_method_call(seg, receiver, args, fixup);
}
ast::ExprKind::Binary(op, lhs, rhs) => {
self.print_expr_binary(*op, lhs, rhs, fixup);
self.print_expr_binary(op.node, lhs, rhs, fixup);
}
ast::ExprKind::Unary(op, expr) => {
self.print_expr_unary(*op, expr, fixup);
@ -605,8 +605,7 @@ impl<'a> State<'a> {
fixup.leftmost_subexpression(),
);
self.space();
self.word(op.node.as_str());
self.word_space("=");
self.word_space(op.node.as_str());
self.print_expr_cond_paren(
rhs,
rhs.precedence() < ExprPrecedence::Assign,

View file

@ -162,13 +162,6 @@ borrowck_opaque_type_lifetime_mismatch =
.prev_lifetime_label = lifetime `{$prev}` previously used here
.note = if all non-lifetime generic parameters are the same, but the lifetime parameters differ, it is not possible to differentiate the opaque types
borrowck_opaque_type_non_generic_param =
expected generic {$kind} parameter, found `{$ty}`
.label = {STREQ($ty, "'static") ->
[true] cannot use static lifetime; use a bound lifetime instead or remove the lifetime parameter from the opaque type
*[other] this generic parameter must be used with a generic {$kind} parameter
}
borrowck_partial_var_move_by_use_in_closure =
variable {$is_partial ->
[true] partially moved

View file

@ -181,7 +181,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
let closure = self.add_moved_or_invoked_closure_note(location, used_place, &mut err);
let mut is_loop_move = false;
let mut in_pattern = false;
let mut seen_spans = FxIndexSet::default();
for move_site in &move_site_vec {
@ -204,7 +203,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
self.suggest_ref_or_clone(
mpi,
&mut err,
&mut in_pattern,
move_spans,
moved_place.as_ref(),
&mut has_suggest_reborrow,
@ -256,15 +254,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
let place = &self.move_data.move_paths[mpi].place;
let ty = place.ty(self.body, self.infcx.tcx).ty;
// If we're in pattern, we do nothing in favor of the previous suggestion (#80913).
// Same for if we're in a loop, see #101119.
if is_loop_move & !in_pattern && !matches!(use_spans, UseSpans::ClosureUse { .. }) {
if let ty::Ref(_, _, hir::Mutability::Mut) = ty.kind() {
// We have a `&mut` ref, we need to reborrow on each iteration (#62112).
self.suggest_reborrow(&mut err, span, moved_place);
}
}
if self.infcx.param_env.caller_bounds().iter().any(|c| {
c.as_trait_clause().is_some_and(|pred| {
pred.skip_binder().self_ty() == ty && self.infcx.tcx.is_fn_trait(pred.def_id())
@ -330,7 +319,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
&self,
mpi: MovePathIndex,
err: &mut Diag<'infcx>,
in_pattern: &mut bool,
move_spans: UseSpans<'tcx>,
moved_place: PlaceRef<'tcx>,
has_suggest_reborrow: &mut bool,
@ -545,7 +533,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
&& !move_span.is_dummy()
&& !self.infcx.tcx.sess.source_map().is_imported(move_span)
{
*in_pattern = true;
let mut sugg = vec![(pat.span.shrink_to_lo(), "ref ".to_string())];
if let Some(pat) = finder.parent_pat {
sugg.insert(0, (pat.span.shrink_to_lo(), "ref ".to_string()));

View file

@ -969,7 +969,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
}
};
// If we can detect the expression to be an function or method call where the closure was
// If we can detect the expression to be a function or method call where the closure was
// an argument, we point at the function or method definition argument...
if let Some((callee_def_id, call_span, call_args)) = get_call_details() {
let arg_pos = call_args

View file

@ -35,7 +35,7 @@ use rustc_infer::infer::{
};
use rustc_middle::mir::*;
use rustc_middle::query::Providers;
use rustc_middle::ty::{self, ParamEnv, RegionVid, TyCtxt, TypingMode, fold_regions};
use rustc_middle::ty::{self, ParamEnv, RegionVid, TyCtxt, TypingMode};
use rustc_middle::{bug, span_bug};
use rustc_mir_dataflow::impls::{
EverInitializedPlaces, MaybeInitializedPlaces, MaybeUninitializedPlaces,
@ -171,12 +171,6 @@ fn do_mir_borrowck<'tcx>(
let free_regions = nll::replace_regions_in_mir(&infcx, &mut body_owned, &mut promoted);
let body = &body_owned; // no further changes
// FIXME(-Znext-solver): A bit dubious that we're only registering
// predefined opaques in the typeck root.
if infcx.next_trait_solver() && !infcx.tcx.is_typeck_child(body.source.def_id()) {
infcx.register_predefined_opaques_for_next_solver(def);
}
let location_table = PoloniusLocationTable::new(body);
let move_data = MoveData::gather_moves(body, tcx, |_| true);
@ -431,7 +425,12 @@ pub(crate) struct BorrowckInferCtxt<'tcx> {
impl<'tcx> BorrowckInferCtxt<'tcx> {
pub(crate) fn new(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Self {
let infcx = tcx.infer_ctxt().build(TypingMode::analysis_in_body(tcx, def_id));
let typing_mode = if tcx.use_typing_mode_borrowck() {
TypingMode::borrowck(tcx, def_id)
} else {
TypingMode::analysis_in_body(tcx, def_id)
};
let infcx = tcx.infer_ctxt().build(typing_mode);
let param_env = tcx.param_env(def_id);
BorrowckInferCtxt { infcx, reg_var_to_origin: RefCell::new(Default::default()), param_env }
}
@ -478,29 +477,6 @@ impl<'tcx> BorrowckInferCtxt<'tcx> {
next_region
}
/// With the new solver we prepopulate the opaque type storage during
/// MIR borrowck with the hidden types from HIR typeck. This is necessary
/// to avoid ambiguities as earlier goals can rely on the hidden type
/// of an opaque which is only constrained by a later goal.
fn register_predefined_opaques_for_next_solver(&self, def_id: LocalDefId) {
let tcx = self.tcx;
// OK to use the identity arguments for each opaque type key, since
// we remap opaques from HIR typeck back to their definition params.
for data in tcx.typeck(def_id).concrete_opaque_types.iter().map(|(k, v)| (*k, *v)) {
// HIR typeck did not infer the regions of the opaque, so we instantiate
// them with fresh inference variables.
let (key, hidden_ty) = fold_regions(tcx, data, |_, _| {
self.next_nll_region_var_in_universe(
NllRegionVariableOrigin::Existential { from_forall: false },
ty::UniverseIndex::ROOT,
)
});
let prev = self.register_hidden_type_in_storage(key, hidden_ty);
assert_eq!(prev, None);
}
}
}
impl<'tcx> Deref for BorrowckInferCtxt<'tcx> {

View file

@ -1,22 +1,17 @@
use rustc_data_structures::fx::FxIndexMap;
use rustc_errors::ErrorGuaranteed;
use rustc_hir::OpaqueTyOrigin;
use rustc_hir::def_id::LocalDefId;
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin, TyCtxtInferExt as _};
use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin};
use rustc_macros::extension;
use rustc_middle::ty::{
self, GenericArgKind, GenericArgs, OpaqueHiddenType, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable,
TypeVisitableExt, TypingMode, fold_regions,
self, DefiningScopeKind, OpaqueHiddenType, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable,
TypeVisitableExt, fold_regions,
};
use rustc_span::Span;
use rustc_trait_selection::regions::OutlivesEnvironmentBuildExt;
use rustc_trait_selection::traits::ObligationCtxt;
use rustc_trait_selection::opaque_types::check_opaque_type_parameter_valid;
use tracing::{debug, instrument};
use super::RegionInferenceContext;
use crate::opaque_types::ConcreteOpaqueTypes;
use crate::session_diagnostics::{LifetimeMismatchOpaqueParam, NonGenericOpaqueTypeParam};
use crate::session_diagnostics::LifetimeMismatchOpaqueParam;
use crate::universal_regions::RegionClassification;
impl<'tcx> RegionInferenceContext<'tcx> {
@ -272,14 +267,21 @@ impl<'tcx> InferCtxt<'tcx> {
return Ty::new_error(self.tcx, e);
}
if let Err(guar) =
check_opaque_type_parameter_valid(self, opaque_type_key, instantiated_ty.span)
{
if let Err(guar) = check_opaque_type_parameter_valid(
self,
opaque_type_key,
instantiated_ty.span,
DefiningScopeKind::MirBorrowck,
) {
return Ty::new_error(self.tcx, guar);
}
let definition_ty = instantiated_ty
.remap_generic_params_to_declaration_params(opaque_type_key, self.tcx, false)
.remap_generic_params_to_declaration_params(
opaque_type_key,
self.tcx,
DefiningScopeKind::MirBorrowck,
)
.ty;
if let Err(e) = definition_ty.error_reported() {
@ -289,156 +291,3 @@ impl<'tcx> InferCtxt<'tcx> {
definition_ty
}
}
/// Opaque type parameter validity check as documented in the [rustc-dev-guide chapter].
///
/// [rustc-dev-guide chapter]:
/// https://rustc-dev-guide.rust-lang.org/opaque-types-region-infer-restrictions.html
fn check_opaque_type_parameter_valid<'tcx>(
infcx: &InferCtxt<'tcx>,
opaque_type_key: OpaqueTypeKey<'tcx>,
span: Span,
) -> Result<(), ErrorGuaranteed> {
let tcx = infcx.tcx;
let opaque_generics = tcx.generics_of(opaque_type_key.def_id);
let opaque_env = LazyOpaqueTyEnv::new(tcx, opaque_type_key.def_id);
let mut seen_params: FxIndexMap<_, Vec<_>> = FxIndexMap::default();
for (i, arg) in opaque_type_key.iter_captured_args(tcx) {
let arg_is_param = match arg.unpack() {
GenericArgKind::Type(ty) => matches!(ty.kind(), ty::Param(_)),
GenericArgKind::Lifetime(lt) => {
matches!(*lt, ty::ReEarlyParam(_) | ty::ReLateParam(_))
|| (lt.is_static() && opaque_env.param_equal_static(i))
}
GenericArgKind::Const(ct) => matches!(ct.kind(), ty::ConstKind::Param(_)),
};
if arg_is_param {
// Register if the same lifetime appears multiple times in the generic args.
// There is an exception when the opaque type *requires* the lifetimes to be equal.
// See [rustc-dev-guide chapter] § "An exception to uniqueness rule".
let seen_where = seen_params.entry(arg).or_default();
if !seen_where.first().is_some_and(|&prev_i| opaque_env.params_equal(i, prev_i)) {
seen_where.push(i);
}
} else {
// Prevent `fn foo() -> Foo<u32>` from being defining.
let opaque_param = opaque_generics.param_at(i, tcx);
let kind = opaque_param.kind.descr();
opaque_env.param_is_error(i)?;
return Err(infcx.dcx().emit_err(NonGenericOpaqueTypeParam {
ty: arg,
kind,
span,
param_span: tcx.def_span(opaque_param.def_id),
}));
}
}
for (_, indices) in seen_params {
if indices.len() > 1 {
let descr = opaque_generics.param_at(indices[0], tcx).kind.descr();
let spans: Vec<_> = indices
.into_iter()
.map(|i| tcx.def_span(opaque_generics.param_at(i, tcx).def_id))
.collect();
#[allow(rustc::diagnostic_outside_of_impl)]
#[allow(rustc::untranslatable_diagnostic)]
return Err(infcx
.dcx()
.struct_span_err(span, "non-defining opaque type use in defining scope")
.with_span_note(spans, format!("{descr} used multiple times"))
.emit());
}
}
Ok(())
}
/// Computes if an opaque type requires a lifetime parameter to be equal to
/// another one or to the `'static` lifetime.
/// These requirements are derived from the explicit and implied bounds.
struct LazyOpaqueTyEnv<'tcx> {
tcx: TyCtxt<'tcx>,
def_id: LocalDefId,
/// Equal parameters will have the same name. Computed Lazily.
/// Example:
/// `type Opaque<'a: 'static, 'b: 'c, 'c: 'b> = impl Sized;`
/// Identity args: `['a, 'b, 'c]`
/// Canonical args: `['static, 'b, 'b]`
canonical_args: std::cell::OnceCell<ty::GenericArgsRef<'tcx>>,
}
impl<'tcx> LazyOpaqueTyEnv<'tcx> {
fn new(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Self {
Self { tcx, def_id, canonical_args: std::cell::OnceCell::new() }
}
fn param_equal_static(&self, param_index: usize) -> bool {
self.get_canonical_args()[param_index].expect_region().is_static()
}
fn params_equal(&self, param1: usize, param2: usize) -> bool {
let canonical_args = self.get_canonical_args();
canonical_args[param1] == canonical_args[param2]
}
fn param_is_error(&self, param_index: usize) -> Result<(), ErrorGuaranteed> {
self.get_canonical_args()[param_index].error_reported()
}
fn get_canonical_args(&self) -> ty::GenericArgsRef<'tcx> {
if let Some(&canonical_args) = self.canonical_args.get() {
return canonical_args;
}
let &Self { tcx, def_id, .. } = self;
let origin = tcx.local_opaque_ty_origin(def_id);
let parent = match origin {
OpaqueTyOrigin::FnReturn { parent, .. }
| OpaqueTyOrigin::AsyncFn { parent, .. }
| OpaqueTyOrigin::TyAlias { parent, .. } => parent,
};
let param_env = tcx.param_env(parent);
let args = GenericArgs::identity_for_item(tcx, parent).extend_to(
tcx,
def_id.to_def_id(),
|param, _| {
tcx.map_opaque_lifetime_to_parent_lifetime(param.def_id.expect_local()).into()
},
);
// FIXME(#132279): It feels wrong to use `non_body_analysis` here given that we're
// in a body here.
let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis());
let ocx = ObligationCtxt::new(&infcx);
let wf_tys = ocx.assumed_wf_types(param_env, parent).unwrap_or_else(|_| {
tcx.dcx().span_delayed_bug(tcx.def_span(def_id), "error getting implied bounds");
Default::default()
});
let outlives_env = OutlivesEnvironment::new(&infcx, parent, param_env, wf_tys);
let mut seen = vec![tcx.lifetimes.re_static];
let canonical_args = fold_regions(tcx, args, |r1, _| {
if r1.is_error() {
r1
} else if let Some(&r2) = seen.iter().find(|&&r2| {
let free_regions = outlives_env.free_region_map();
free_regions.sub_free_regions(tcx, r1, r2)
&& free_regions.sub_free_regions(tcx, r2, r1)
}) {
r2
} else {
seen.push(r1);
r1
}
});
self.canonical_args.set(canonical_args).unwrap();
canonical_args
}
}

View file

@ -294,17 +294,6 @@ pub(crate) struct MoveBorrow<'a> {
pub borrow_span: Span,
}
#[derive(Diagnostic)]
#[diag(borrowck_opaque_type_non_generic_param, code = E0792)]
pub(crate) struct NonGenericOpaqueTypeParam<'a, 'tcx> {
pub ty: GenericArg<'tcx>,
pub kind: &'a str,
#[primary_span]
pub span: Span,
#[label]
pub param_span: Span,
}
#[derive(Diagnostic)]
#[diag(borrowck_opaque_type_lifetime_mismatch)]
pub(crate) struct LifetimeMismatchOpaqueParam<'tcx> {

View file

@ -79,6 +79,7 @@ builtin_macros_autodiff_ret_activity = invalid return activity {$act} in {$mode}
builtin_macros_autodiff_ty_activity = {$act} can not be used for this type
builtin_macros_autodiff_unknown_activity = did not recognize Activity: `{$act}`
builtin_macros_autodiff_width = autodiff width must fit u32, but is {$width}
builtin_macros_bad_derive_target = `derive` may only be applied to `struct`s, `enum`s and `union`s
.label = not applicable here
.label2 = not a `struct`, `enum` or `union`

View file

@ -12,12 +12,12 @@ mod llvm_enzyme {
valid_ty_for_activity,
};
use rustc_ast::ptr::P;
use rustc_ast::token::{Token, TokenKind};
use rustc_ast::token::{Lit, LitKind, Token, TokenKind};
use rustc_ast::tokenstream::*;
use rustc_ast::visit::AssocCtxt::*;
use rustc_ast::{
self as ast, AssocItemKind, BindingMode, FnRetTy, FnSig, Generics, ItemKind, MetaItemInner,
PatKind, TyKind,
self as ast, AssocItemKind, BindingMode, ExprKind, FnRetTy, FnSig, Generics, ItemKind,
MetaItemInner, PatKind, QSelf, TyKind,
};
use rustc_expand::base::{Annotatable, ExtCtxt};
use rustc_span::{Ident, Span, Symbol, kw, sym};
@ -45,6 +45,16 @@ mod llvm_enzyme {
}
}
fn first_ident(x: &MetaItemInner) -> rustc_span::Ident {
if let Some(l) = x.lit() {
match l.kind {
ast::LitKind::Int(val, _) => {
// get an Ident from a lit
return rustc_span::Ident::from_str(val.get().to_string().as_str());
}
_ => {}
}
}
let segments = &x.meta_item().unwrap().path.segments;
assert!(segments.len() == 1);
segments[0].ident
@ -54,6 +64,14 @@ mod llvm_enzyme {
first_ident(x).name.to_string()
}
fn width(x: &MetaItemInner) -> Option<u128> {
let lit = x.lit()?;
match lit.kind {
ast::LitKind::Int(x, _) => Some(x.get()),
_ => return None,
}
}
pub(crate) fn from_ast(
ecx: &mut ExtCtxt<'_>,
meta_item: &ThinVec<MetaItemInner>,
@ -65,9 +83,32 @@ mod llvm_enzyme {
dcx.emit_err(errors::AutoDiffInvalidMode { span: meta_item[1].span(), mode });
return AutoDiffAttrs::error();
};
// Now we check, whether the user wants autodiff in batch/vector mode, or scalar mode.
// If he doesn't specify an integer (=width), we default to scalar mode, thus width=1.
let mut first_activity = 2;
let width = if let [_, _, x, ..] = &meta_item[..]
&& let Some(x) = width(x)
{
first_activity = 3;
match x.try_into() {
Ok(x) => x,
Err(_) => {
dcx.emit_err(errors::AutoDiffInvalidWidth {
span: meta_item[2].span(),
width: x,
});
return AutoDiffAttrs::error();
}
}
} else {
1
};
let mut activities: Vec<DiffActivity> = vec![];
let mut errors = false;
for x in &meta_item[2..] {
for x in &meta_item[first_activity..] {
let activity_str = name(&x);
let res = DiffActivity::from_str(&activity_str);
match res {
@ -98,7 +139,20 @@ mod llvm_enzyme {
(&DiffActivity::None, activities.as_slice())
};
AutoDiffAttrs { mode, ret_activity: *ret_activity, input_activity: input_activity.to_vec() }
AutoDiffAttrs {
mode,
width,
ret_activity: *ret_activity,
input_activity: input_activity.to_vec(),
}
}
fn meta_item_inner_to_ts(t: &MetaItemInner, ts: &mut Vec<TokenTree>) {
let comma: Token = Token::new(TokenKind::Comma, Span::default());
let val = first_ident(t);
let t = Token::from_ast_ident(val);
ts.push(TokenTree::Token(t, Spacing::Joint));
ts.push(TokenTree::Token(comma.clone(), Spacing::Alone));
}
/// We expand the autodiff macro to generate a new placeholder function which passes
@ -195,27 +249,49 @@ mod llvm_enzyme {
// create TokenStream from vec elemtents:
// meta_item doesn't have a .tokens field
let comma: Token = Token::new(TokenKind::Comma, Span::default());
let mut ts: Vec<TokenTree> = vec![];
if meta_item_vec.len() < 2 {
// At the bare minimum, we need a fnc name and a mode, even for a dummy function with no
// input and output args.
dcx.emit_err(errors::AutoDiffMissingConfig { span: item.span() });
return vec![item];
} else {
for t in meta_item_vec.clone()[1..].iter() {
let val = first_ident(t);
let t = Token::from_ast_ident(val);
ts.push(TokenTree::Token(t, Spacing::Joint));
ts.push(TokenTree::Token(comma.clone(), Spacing::Alone));
}
}
meta_item_inner_to_ts(&meta_item_vec[1], &mut ts);
// Now, if the user gave a width (vector aka batch-mode ad), then we copy it.
// If it is not given, we default to 1 (scalar mode).
let start_position;
let kind: LitKind = LitKind::Integer;
let symbol;
if meta_item_vec.len() >= 3
&& let Some(width) = width(&meta_item_vec[2])
{
start_position = 3;
symbol = Symbol::intern(&width.to_string());
} else {
start_position = 2;
symbol = sym::integer(1);
}
let l: Lit = Lit { kind, symbol, suffix: None };
let t = Token::new(TokenKind::Literal(l), Span::default());
let comma = Token::new(TokenKind::Comma, Span::default());
ts.push(TokenTree::Token(t, Spacing::Joint));
ts.push(TokenTree::Token(comma.clone(), Spacing::Alone));
for t in meta_item_vec.clone()[start_position..].iter() {
meta_item_inner_to_ts(t, &mut ts);
}
if !has_ret {
// We don't want users to provide a return activity if the function doesn't return anything.
// For simplicity, we just add a dummy token to the end of the list.
let t = Token::new(TokenKind::Ident(sym::None, false.into()), Span::default());
ts.push(TokenTree::Token(t, Spacing::Joint));
ts.push(TokenTree::Token(comma, Spacing::Alone));
}
// We remove the last, trailing comma.
ts.pop();
let ts: TokenStream = TokenStream::from_iter(ts);
let x: AutoDiffAttrs = from_ast(ecx, &meta_item_vec, has_ret);
@ -470,6 +546,8 @@ mod llvm_enzyme {
return body;
}
// Everything from here onwards just tries to fullfil the return type. Fun!
// having an active-only return means we'll drop the original return type.
// So that can be treated identical to not having one in the first place.
let primal_ret = has_ret(&sig.decl.output) && !x.has_active_only_ret();
@ -497,86 +575,65 @@ mod llvm_enzyme {
return body;
}
let mut exprs = ThinVec::<P<ast::Expr>>::new();
if primal_ret {
// We have both primal ret and active floats.
// primal ret is first, by construction.
exprs.push(primal_call);
}
// Now construct default placeholder for each active float.
// Is there something nicer than f32::default() and f64::default()?
let mut exprs: P<ast::Expr> = primal_call.clone();
let d_ret_ty = match d_sig.decl.output {
FnRetTy::Ty(ref ty) => ty.clone(),
FnRetTy::Default(span) => {
panic!("Did not expect Default ret ty: {:?}", span);
}
};
let mut d_ret_ty = match d_ret_ty.kind.clone() {
TyKind::Tup(ref tys) => tys.clone(),
TyKind::Path(_, rustc_ast::Path { segments, .. }) => {
if let [segment] = &segments[..]
&& segment.args.is_none()
{
let id = vec![segments[0].ident];
let kind = TyKind::Path(None, ecx.path(span, id));
let ty = P(rustc_ast::Ty { kind, id: ast::DUMMY_NODE_ID, span, tokens: None });
thin_vec![ty]
} else {
panic!("Expected tuple or simple path return type");
}
}
_ => {
// We messed up construction of d_sig
panic!("Did not expect non-tuple ret ty: {:?}", d_ret_ty);
}
};
if x.mode.is_fwd() && x.ret_activity == DiffActivity::Dual {
assert!(d_ret_ty.len() == 2);
// both should be identical, by construction
let arg = d_ret_ty[0].kind.is_simple_path().unwrap();
let arg2 = d_ret_ty[1].kind.is_simple_path().unwrap();
assert!(arg == arg2);
let sl: Vec<Symbol> = vec![arg, kw::Default];
let tmp = ecx.def_site_path(&sl);
let default_call_expr = ecx.expr_path(ecx.path(span, tmp));
let default_call_expr = ecx.expr_call(new_decl_span, default_call_expr, thin_vec![]);
exprs.push(default_call_expr);
} else if x.mode.is_rev() {
if primal_ret {
// We have extra handling above for the primal ret
d_ret_ty = d_ret_ty[1..].to_vec().into();
}
for arg in d_ret_ty.iter() {
let arg = arg.kind.is_simple_path().unwrap();
let sl: Vec<Symbol> = vec![arg, kw::Default];
let tmp = ecx.def_site_path(&sl);
let default_call_expr = ecx.expr_path(ecx.path(span, tmp));
if x.mode.is_fwd() {
// Fwd mode is easy. If the return activity is Const, we support arbitrary types.
// Otherwise, we only support a scalar, a pair of scalars, or an array of scalars.
// We checked that (on a best-effort base) in the preceding gen_enzyme_decl function.
// In all three cases, we can return `std::hint::black_box(<T>::default())`.
if x.ret_activity == DiffActivity::Const {
// Here we call the primal function, since our dummy function has the same return
// type due to the Const return activity.
exprs = ecx.expr_call(new_decl_span, bb_call_expr, thin_vec![exprs]);
} else {
let q = QSelf { ty: d_ret_ty.clone(), path_span: span, position: 0 };
let y =
ExprKind::Path(Some(P(q)), ecx.path_ident(span, Ident::from_str("default")));
let default_call_expr = ecx.expr(span, y);
let default_call_expr =
ecx.expr_call(new_decl_span, default_call_expr, thin_vec![]);
exprs.push(default_call_expr);
exprs = ecx.expr_call(new_decl_span, bb_call_expr, thin_vec![default_call_expr]);
}
} else if x.mode.is_rev() {
if x.width == 1 {
// We either have `-> ArbitraryType` or `-> (ArbitraryType, repeated_float_scalars)`.
match d_ret_ty.kind {
TyKind::Tup(ref args) => {
// We have a tuple return type. We need to create a tuple of the same size
// and fill it with default values.
let mut exprs2 = thin_vec![exprs];
for arg in args.iter().skip(1) {
let arg = arg.kind.is_simple_path().unwrap();
let sl: Vec<Symbol> = vec![arg, kw::Default];
let tmp = ecx.def_site_path(&sl);
let default_call_expr = ecx.expr_path(ecx.path(span, tmp));
let default_call_expr =
ecx.expr_call(new_decl_span, default_call_expr, thin_vec![]);
exprs2.push(default_call_expr);
}
exprs = ecx.expr_tuple(new_decl_span, exprs2);
}
_ => {
// Interestingly, even the `-> ArbitraryType` case
// ends up getting matched and handled correctly above,
// so we don't have to handle any other case for now.
panic!("Unsupported return type: {:?}", d_ret_ty);
}
}
}
exprs = ecx.expr_call(new_decl_span, bb_call_expr, thin_vec![exprs]);
} else {
unreachable!("Unsupported mode: {:?}", x.mode);
}
let ret: P<ast::Expr>;
match &exprs[..] {
[] => {
assert!(!has_ret(&d_sig.decl.output));
// We don't have to match the return type.
return body;
}
[arg] => {
ret = ecx.expr_call(new_decl_span, bb_call_expr, thin_vec![arg.clone()]);
}
args => {
let ret_tuple: P<ast::Expr> = ecx.expr_tuple(span, args.into());
ret = ecx.expr_call(new_decl_span, bb_call_expr, thin_vec![ret_tuple]);
}
}
assert!(has_ret(&d_sig.decl.output));
body.stmts.push(ecx.stmt_expr(ret));
body.stmts.push(ecx.stmt_expr(exprs));
body
}
@ -684,50 +741,55 @@ mod llvm_enzyme {
match activity {
DiffActivity::Active => {
act_ret.push(arg.ty.clone());
// if width =/= 1, then push [arg.ty; width] to act_ret
}
DiffActivity::ActiveOnly => {
// We will add the active scalar to the return type.
// This is handled later.
}
DiffActivity::Duplicated | DiffActivity::DuplicatedOnly => {
let mut shadow_arg = arg.clone();
// We += into the shadow in reverse mode.
shadow_arg.ty = P(assure_mut_ref(&arg.ty));
let old_name = if let PatKind::Ident(_, ident, _) = arg.pat.kind {
ident.name
} else {
debug!("{:#?}", &shadow_arg.pat);
panic!("not an ident?");
};
let name: String = format!("d{}", old_name);
new_inputs.push(name.clone());
let ident = Ident::from_str_and_span(&name, shadow_arg.pat.span);
shadow_arg.pat = P(ast::Pat {
id: ast::DUMMY_NODE_ID,
kind: PatKind::Ident(BindingMode::NONE, ident, None),
span: shadow_arg.pat.span,
tokens: shadow_arg.pat.tokens.clone(),
});
d_inputs.push(shadow_arg);
for i in 0..x.width {
let mut shadow_arg = arg.clone();
// We += into the shadow in reverse mode.
shadow_arg.ty = P(assure_mut_ref(&arg.ty));
let old_name = if let PatKind::Ident(_, ident, _) = arg.pat.kind {
ident.name
} else {
debug!("{:#?}", &shadow_arg.pat);
panic!("not an ident?");
};
let name: String = format!("d{}_{}", old_name, i);
new_inputs.push(name.clone());
let ident = Ident::from_str_and_span(&name, shadow_arg.pat.span);
shadow_arg.pat = P(ast::Pat {
id: ast::DUMMY_NODE_ID,
kind: PatKind::Ident(BindingMode::NONE, ident, None),
span: shadow_arg.pat.span,
tokens: shadow_arg.pat.tokens.clone(),
});
d_inputs.push(shadow_arg.clone());
}
}
DiffActivity::Dual | DiffActivity::DualOnly => {
let mut shadow_arg = arg.clone();
let old_name = if let PatKind::Ident(_, ident, _) = arg.pat.kind {
ident.name
} else {
debug!("{:#?}", &shadow_arg.pat);
panic!("not an ident?");
};
let name: String = format!("b{}", old_name);
new_inputs.push(name.clone());
let ident = Ident::from_str_and_span(&name, shadow_arg.pat.span);
shadow_arg.pat = P(ast::Pat {
id: ast::DUMMY_NODE_ID,
kind: PatKind::Ident(BindingMode::NONE, ident, None),
span: shadow_arg.pat.span,
tokens: shadow_arg.pat.tokens.clone(),
});
d_inputs.push(shadow_arg);
for i in 0..x.width {
let mut shadow_arg = arg.clone();
let old_name = if let PatKind::Ident(_, ident, _) = arg.pat.kind {
ident.name
} else {
debug!("{:#?}", &shadow_arg.pat);
panic!("not an ident?");
};
let name: String = format!("b{}_{}", old_name, i);
new_inputs.push(name.clone());
let ident = Ident::from_str_and_span(&name, shadow_arg.pat.span);
shadow_arg.pat = P(ast::Pat {
id: ast::DUMMY_NODE_ID,
kind: PatKind::Ident(BindingMode::NONE, ident, None),
span: shadow_arg.pat.span,
tokens: shadow_arg.pat.tokens.clone(),
});
d_inputs.push(shadow_arg.clone());
}
}
DiffActivity::Const => {
// Nothing to do here.
@ -783,23 +845,48 @@ mod llvm_enzyme {
d_decl.inputs = d_inputs.into();
if x.mode.is_fwd() {
let ty = match d_decl.output {
FnRetTy::Ty(ref ty) => ty.clone(),
FnRetTy::Default(span) => {
// We want to return std::hint::black_box(()).
let kind = TyKind::Tup(ThinVec::new());
let ty = P(rustc_ast::Ty { kind, id: ast::DUMMY_NODE_ID, span, tokens: None });
d_decl.output = FnRetTy::Ty(ty.clone());
assert!(matches!(x.ret_activity, DiffActivity::None));
// this won't be used below, so any type would be fine.
ty
}
};
if let DiffActivity::Dual = x.ret_activity {
let ty = match d_decl.output {
FnRetTy::Ty(ref ty) => ty.clone(),
FnRetTy::Default(span) => {
panic!("Did not expect Default ret ty: {:?}", span);
}
let kind = if x.width == 1 {
// Dual can only be used for f32/f64 ret.
// In that case we return now a tuple with two floats.
TyKind::Tup(thin_vec![ty.clone(), ty.clone()])
} else {
// We have to return [T; width+1], +1 for the primal return.
let anon_const = rustc_ast::AnonConst {
id: ast::DUMMY_NODE_ID,
value: ecx.expr_usize(span, 1 + x.width as usize),
};
TyKind::Array(ty.clone(), anon_const)
};
// Dual can only be used for f32/f64 ret.
// In that case we return now a tuple with two floats.
let kind = TyKind::Tup(thin_vec![ty.clone(), ty.clone()]);
let ty = P(rustc_ast::Ty { kind, id: ty.id, span: ty.span, tokens: None });
d_decl.output = FnRetTy::Ty(ty);
}
if let DiffActivity::DualOnly = x.ret_activity {
// No need to change the return type,
// we will just return the shadow in place
// of the primal return.
// we will just return the shadow in place of the primal return.
// However, if we have a width > 1, then we don't return -> T, but -> [T; width]
if x.width > 1 {
let anon_const = rustc_ast::AnonConst {
id: ast::DUMMY_NODE_ID,
value: ecx.expr_usize(span, x.width as usize),
};
let kind = TyKind::Array(ty.clone(), anon_const);
let ty = P(rustc_ast::Ty { kind, id: ty.id, span: ty.span, tokens: None });
d_decl.output = FnRetTy::Ty(ty);
}
}
}

View file

@ -202,6 +202,14 @@ mod autodiff {
pub(crate) mode: String,
}
#[derive(Diagnostic)]
#[diag(builtin_macros_autodiff_width)]
pub(crate) struct AutoDiffInvalidWidth {
#[primary_span]
pub(crate) span: Span,
pub(crate) width: u128,
}
#[derive(Diagnostic)]
#[diag(builtin_macros_autodiff)]
pub(crate) struct AutoDiffInvalidApplication {

View file

@ -99,6 +99,34 @@ const BASE_SYSROOT_SUITE: &[TestCase] = &[
runner.run_out_command("gen_block_iterate", &[]);
}),
TestCase::build_bin_and_run("aot.raw-dylib", "example/raw-dylib.rs", &[]),
TestCase::custom("test.sysroot", &|runner| {
apply_patches(
&runner.dirs,
"sysroot_tests",
&runner.stdlib_source.join("library"),
&SYSROOT_TESTS_SRC.to_path(&runner.dirs),
);
SYSROOT_TESTS.clean(&runner.dirs);
let mut target_compiler = runner.target_compiler.clone();
// coretests and alloctests produce a bunch of warnings. When running
// in rust's CI warnings are denied, so we have to override that here.
target_compiler.rustflags.push("--cap-lints=allow".to_owned());
// The standard library may have been compiled with -Zrandomize-layout.
target_compiler.rustflags.extend(["--cfg".to_owned(), "randomized_layouts".to_owned()]);
if runner.is_native {
let mut test_cmd = SYSROOT_TESTS.test(&target_compiler, &runner.dirs);
test_cmd.args(["-p", "coretests", "-p", "alloctests", "--tests", "--", "-q"]);
spawn_and_wait(test_cmd);
} else {
eprintln!("Cross-Compiling: Not running tests");
let mut build_cmd = SYSROOT_TESTS.build(&target_compiler, &runner.dirs);
build_cmd.args(["-p", "coretests", "-p", "alloctests", "--tests"]);
spawn_and_wait(build_cmd);
}
}),
];
pub(crate) static RAND_REPO: GitRepo = GitRepo::github(
@ -146,27 +174,6 @@ const EXTENDED_SYSROOT_SUITE: &[TestCase] = &[
spawn_and_wait(build_cmd);
}
}),
TestCase::custom("test.sysroot", &|runner| {
apply_patches(
&runner.dirs,
"sysroot_tests",
&runner.stdlib_source.join("library"),
&SYSROOT_TESTS_SRC.to_path(&runner.dirs),
);
SYSROOT_TESTS.clean(&runner.dirs);
if runner.is_native {
let mut test_cmd = SYSROOT_TESTS.test(&runner.target_compiler, &runner.dirs);
test_cmd.args(["-p", "coretests", "-p", "alloctests", "--", "-q"]);
spawn_and_wait(test_cmd);
} else {
eprintln!("Cross-Compiling: Not running tests");
let mut build_cmd = SYSROOT_TESTS.build(&runner.target_compiler, &runner.dirs);
build_cmd.args(["-p", "coretests", "-p", "alloctests", "--tests"]);
spawn_and_wait(build_cmd);
}
}),
TestCase::custom("test.regex", &|runner| {
REGEX_REPO.patch(&runner.dirs);

View file

@ -105,7 +105,11 @@ impl CargoProject {
.arg(self.manifest_path(dirs))
.arg("--target-dir")
.arg(self.target_dir(dirs))
.arg("--locked");
.arg("--locked")
// bootstrap sets both RUSTC and RUSTC_WRAPPER to the same wrapper. RUSTC is already
// respected by the rustc-clif wrapper, but RUSTC_WRAPPER will misinterpret rustc-clif
// as filename, so we need to unset it.
.env_remove("RUSTC_WRAPPER");
if dirs.frozen {
cmd.arg("--frozen");

View file

@ -32,9 +32,9 @@ aot.issue-59326
aot.neon
aot.gen_block_iterate
aot.raw-dylib
test.sysroot
testsuite.extended_sysroot
test.rust-random/rand
test.sysroot
test.regex
test.portable-simd

View file

@ -641,7 +641,7 @@ pub(crate) fn codegen_terminator_call<'tcx>(
.flat_map(|arg_abi| arg_abi.get_abi_param(fx.tcx).into_iter()),
);
if fx.tcx.sess.target.is_like_osx && fx.tcx.sess.target.arch == "aarch64" {
if fx.tcx.sess.target.is_like_darwin && fx.tcx.sess.target.arch == "aarch64" {
// Add any padding arguments needed for Apple AArch64.
// There's no need to pad the argument list unless variadic arguments are actually being
// passed.

View file

@ -391,7 +391,7 @@ fn define_all_allocs(tcx: TyCtxt<'_>, module: &mut dyn Module, cx: &mut Constant
data.set_align(alloc.align.bytes());
if let Some(section_name) = section_name {
let (segment_name, section_name) = if tcx.sess.target.is_like_osx {
let (segment_name, section_name) = if tcx.sess.target.is_like_darwin {
// See https://github.com/llvm/llvm-project/blob/main/llvm/lib/MC/MCSectionMachO.cpp
let mut parts = section_name.as_str().split(',');
let Some(segment_name) = parts.next() else {

View file

@ -58,7 +58,7 @@ impl DebugContext {
// FIXME this should be configurable
// macOS doesn't seem to support DWARF > 3
// 5 version is required for md5 file hash
version: if tcx.sess.target.is_like_osx {
version: if tcx.sess.target.is_like_darwin {
3
} else {
// FIXME change to version 5 once the gdb and lldb shipping with the latest debian

View file

@ -131,7 +131,7 @@ impl<'gcc, 'tcx> StaticCodegenMethods for CodegenCx<'gcc, 'tcx> {
// will use load-unaligned instructions instead, and thus avoiding the crash.
//
// We could remove this hack whenever we decide to drop macOS 10.10 support.
if self.tcx.sess.target.options.is_like_osx {
if self.tcx.sess.target.options.is_like_darwin {
// The `inspect` method is okay here because we checked for provenance, and
// because we are doing this access to inspect the final interpreter state
// (not as part of the interpreter execution).

View file

@ -56,6 +56,8 @@ codegen_llvm_prepare_thin_lto_module_with_llvm_err = failed to prepare thin LTO
codegen_llvm_run_passes = failed to run LLVM passes
codegen_llvm_run_passes_with_llvm_err = failed to run LLVM passes: {$llvm_err}
codegen_llvm_sanitizer_kcfi_arity_requires_llvm_21_0_0 = `-Zsanitizer-kcfi-arity` requires LLVM 21.0.0 or later.
codegen_llvm_sanitizer_memtag_requires_mte =
`-Zsanitizer=memtag` requires `-Ctarget-feature=+mte`

View file

@ -17,14 +17,13 @@ use rustc_target::callconv::{
use rustc_target::spec::SanitizerSet;
use smallvec::SmallVec;
use crate::attributes::llfn_attrs_from_instance;
use crate::attributes::{self, llfn_attrs_from_instance};
use crate::builder::Builder;
use crate::context::CodegenCx;
use crate::llvm::{self, Attribute, AttributePlace};
use crate::type_::Type;
use crate::type_of::LayoutLlvmExt;
use crate::value::Value;
use crate::{attributes, llvm_util};
trait ArgAttributesExt {
fn apply_attrs_to_llfn(&self, idx: AttributePlace, cx: &CodegenCx<'_, '_>, llfn: &Value);
@ -437,7 +436,6 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> {
let apply_range_attr = |idx: AttributePlace, scalar: rustc_abi::Scalar| {
if cx.sess().opts.optimize != config::OptLevel::No
&& llvm_util::get_version() >= (19, 0, 0)
&& matches!(scalar.primitive(), Primitive::Int(..))
// If the value is a boolean, the range is 0..2 and that ultimately
// become 0..0 when the type becomes i1, which would be rejected
@ -571,19 +569,6 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> {
}
_ => {}
}
if bx.cx.sess().opts.optimize != config::OptLevel::No
&& llvm_util::get_version() < (19, 0, 0)
&& let BackendRepr::Scalar(scalar) = self.ret.layout.backend_repr
&& matches!(scalar.primitive(), Primitive::Int(..))
// If the value is a boolean, the range is 0..2 and that ultimately
// become 0..0 when the type becomes i1, which would be rejected
// by the LLVM verifier.
&& !scalar.is_bool()
// LLVM also rejects full range.
&& !scalar.is_always_valid(bx)
{
bx.range_metadata(callsite, scalar.valid_range(bx));
}
for arg in self.args.iter() {
match &arg.mode {
PassMode::Ignore => {}

View file

@ -407,30 +407,28 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>(
// Do not set sanitizer attributes for naked functions.
to_add.extend(sanitize_attrs(cx, codegen_fn_attrs.no_sanitize));
if llvm_util::get_version() >= (19, 0, 0) {
// For non-naked functions, set branch protection attributes on aarch64.
if let Some(BranchProtection { bti, pac_ret }) =
cx.sess().opts.unstable_opts.branch_protection
{
assert!(cx.sess().target.arch == "aarch64");
if bti {
to_add.push(llvm::CreateAttrString(cx.llcx, "branch-target-enforcement"));
}
if let Some(PacRet { leaf, pc, key }) = pac_ret {
if pc {
to_add.push(llvm::CreateAttrString(cx.llcx, "branch-protection-pauth-lr"));
}
to_add.push(llvm::CreateAttrStringValue(
cx.llcx,
"sign-return-address",
if leaf { "all" } else { "non-leaf" },
));
to_add.push(llvm::CreateAttrStringValue(
cx.llcx,
"sign-return-address-key",
if key == PAuthKey::A { "a_key" } else { "b_key" },
));
// For non-naked functions, set branch protection attributes on aarch64.
if let Some(BranchProtection { bti, pac_ret }) =
cx.sess().opts.unstable_opts.branch_protection
{
assert!(cx.sess().target.arch == "aarch64");
if bti {
to_add.push(llvm::CreateAttrString(cx.llcx, "branch-target-enforcement"));
}
if let Some(PacRet { leaf, pc, key }) = pac_ret {
if pc {
to_add.push(llvm::CreateAttrString(cx.llcx, "branch-protection-pauth-lr"));
}
to_add.push(llvm::CreateAttrStringValue(
cx.llcx,
"sign-return-address",
if leaf { "all" } else { "non-leaf" },
));
to_add.push(llvm::CreateAttrStringValue(
cx.llcx,
"sign-return-address-key",
if key == PAuthKey::A { "a_key" } else { "b_key" },
));
}
}
}
@ -510,12 +508,6 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>(
InstructionSetAttr::ArmA32 => "-thumb-mode".to_string(),
InstructionSetAttr::ArmT32 => "+thumb-mode".to_string(),
}))
// HACK: LLVM versions 19+ do not have the FPMR feature and treat it as always enabled
// It only exists as a feature in LLVM 18, cannot be passed down for any other version
.chain(match &*cx.tcx.sess.target.arch {
"aarch64" if llvm_util::get_version().0 == 18 => vec!["+fpmr".to_string()],
_ => vec![],
})
.collect::<Vec<String>>();
if cx.tcx.sess.target.is_like_wasm {

View file

@ -610,6 +610,8 @@ fn enable_autodiff_settings(ad: &[config::AutoDiff], module: &mut ModuleCodegen<
}
// We handle this below
config::AutoDiff::PrintModAfter => {}
// We handle this below
config::AutoDiff::PrintModFinal => {}
// This is required and already checked
config::AutoDiff::Enable => {}
}
@ -657,14 +659,20 @@ pub(crate) fn run_pass_manager(
}
if cfg!(llvm_enzyme) && enable_ad {
// This is the post-autodiff IR, mainly used for testing and educational purposes.
if config.autodiff.contains(&config::AutoDiff::PrintModAfter) {
unsafe { llvm::LLVMDumpModule(module.module_llvm.llmod()) };
}
let opt_stage = llvm::OptStage::FatLTO;
let stage = write::AutodiffStage::PostAD;
unsafe {
write::llvm_optimize(cgcx, dcx, module, None, config, opt_level, opt_stage, stage)?;
}
// This is the final IR, so people should be able to inspect the optimized autodiff output.
if config.autodiff.contains(&config::AutoDiff::PrintModAfter) {
// This is the final IR, so people should be able to inspect the optimized autodiff output,
// for manual inspection.
if config.autodiff.contains(&config::AutoDiff::PrintModFinal) {
unsafe { llvm::LLVMDumpModule(module.module_llvm.llmod()) };
}
}

View file

@ -1024,7 +1024,7 @@ fn create_section_with_flags_asm(section_name: &str, section_flags: &str, data:
}
pub(crate) fn bitcode_section_name(cgcx: &CodegenContext<LlvmCodegenBackend>) -> &'static CStr {
if cgcx.target_is_like_osx {
if cgcx.target_is_like_darwin {
c"__LLVM,__bitcode"
} else if cgcx.target_is_like_aix {
c".ipa"
@ -1077,7 +1077,7 @@ unsafe fn embed_bitcode(
// and COFF we emit the sections using module level inline assembly for that
// reason (see issue #90326 for historical background).
unsafe {
if cgcx.target_is_like_osx
if cgcx.target_is_like_darwin
|| cgcx.target_is_like_aix
|| cgcx.target_arch == "wasm32"
|| cgcx.target_arch == "wasm64"
@ -1096,7 +1096,7 @@ unsafe fn embed_bitcode(
let llglobal =
llvm::add_global(llmod, common::val_ty(llconst), c"rustc.embedded.cmdline");
llvm::set_initializer(llglobal, llconst);
let section = if cgcx.target_is_like_osx {
let section = if cgcx.target_is_like_darwin {
c"__LLVM,__cmdline"
} else if cgcx.target_is_like_aix {
c".info"

View file

@ -30,6 +30,7 @@ use smallvec::SmallVec;
use tracing::{debug, instrument};
use crate::abi::FnAbiLlvmExt;
use crate::attributes;
use crate::common::Funclet;
use crate::context::{CodegenCx, FullCx, GenericCx, SCx};
use crate::llvm::{
@ -38,7 +39,6 @@ use crate::llvm::{
use crate::type_::Type;
use crate::type_of::LayoutLlvmExt;
use crate::value::Value;
use crate::{attributes, llvm_util};
#[must_use]
pub(crate) struct GenericBuilder<'a, 'll, CX: Borrow<SCx<'ll>>> {
@ -927,11 +927,9 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
debug_assert_ne!(self.val_ty(val), dest_ty);
let trunc = self.trunc(val, dest_ty);
if llvm_util::get_version() >= (19, 0, 0) {
unsafe {
if llvm::LLVMIsAInstruction(trunc).is_some() {
llvm::LLVMSetNUW(trunc, True);
}
unsafe {
if llvm::LLVMIsAInstruction(trunc).is_some() {
llvm::LLVMSetNUW(trunc, True);
}
}
trunc
@ -941,11 +939,9 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
debug_assert_ne!(self.val_ty(val), dest_ty);
let trunc = self.trunc(val, dest_ty);
if llvm_util::get_version() >= (19, 0, 0) {
unsafe {
if llvm::LLVMIsAInstruction(trunc).is_some() {
llvm::LLVMSetNSW(trunc, True);
}
unsafe {
if llvm::LLVMIsAInstruction(trunc).is_some() {
llvm::LLVMSetNSW(trunc, True);
}
}
trunc
@ -1899,10 +1895,6 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> {
hash: &'ll Value,
bitmap_bits: &'ll Value,
) {
assert!(
crate::llvm_util::get_version() >= (19, 0, 0),
"MCDC intrinsics require LLVM 19 or later"
);
self.call_intrinsic("llvm.instrprof.mcdc.parameters", &[fn_name, hash, bitmap_bits]);
}
@ -1914,10 +1906,6 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> {
bitmap_index: &'ll Value,
mcdc_temp: &'ll Value,
) {
assert!(
crate::llvm_util::get_version() >= (19, 0, 0),
"MCDC intrinsics require LLVM 19 or later"
);
let args = &[fn_name, hash, bitmap_index, mcdc_temp];
self.call_intrinsic("llvm.instrprof.mcdc.tvbitmap.update", args);
}
@ -1929,10 +1917,6 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> {
#[instrument(level = "debug", skip(self))]
pub(crate) fn mcdc_condbitmap_update(&mut self, cond_index: &'ll Value, mcdc_temp: &'ll Value) {
assert!(
crate::llvm_util::get_version() >= (19, 0, 0),
"MCDC intrinsics require LLVM 19 or later"
);
let align = self.tcx.data_layout.i32_align.abi;
let current_tv_index = self.load(self.cx.type_i32(), mcdc_temp, align);
let new_tv_index = self.add(current_tv_index, cond_index);

View file

@ -3,8 +3,10 @@ use std::ptr;
use rustc_ast::expand::autodiff_attrs::{AutoDiffAttrs, AutoDiffItem, DiffActivity, DiffMode};
use rustc_codegen_ssa::ModuleCodegen;
use rustc_codegen_ssa::back::write::ModuleConfig;
use rustc_codegen_ssa::traits::BaseTypeCodegenMethods as _;
use rustc_codegen_ssa::common::TypeKind;
use rustc_codegen_ssa::traits::BaseTypeCodegenMethods;
use rustc_errors::FatalError;
use rustc_middle::bug;
use tracing::{debug, trace};
use crate::back::write::llvm_err;
@ -18,21 +20,42 @@ use crate::value::Value;
use crate::{CodegenContext, LlvmCodegenBackend, ModuleLlvm, attributes, llvm};
fn get_params(fnc: &Value) -> Vec<&Value> {
let param_num = llvm::LLVMCountParams(fnc) as usize;
let mut fnc_args: Vec<&Value> = vec![];
fnc_args.reserve(param_num);
unsafe {
let param_num = llvm::LLVMCountParams(fnc) as usize;
let mut fnc_args: Vec<&Value> = vec![];
fnc_args.reserve(param_num);
llvm::LLVMGetParams(fnc, fnc_args.as_mut_ptr());
fnc_args.set_len(param_num);
fnc_args
}
fnc_args
}
fn has_sret(fnc: &Value) -> bool {
let num_args = llvm::LLVMCountParams(fnc) as usize;
if num_args == 0 {
false
} else {
unsafe { llvm::LLVMRustHasAttributeAtIndex(fnc, 0, llvm::AttributeKind::StructRet) }
}
}
// When we call the `__enzyme_autodiff` or `__enzyme_fwddiff` function, we need to pass all the
// original inputs, as well as metadata and the additional shadow arguments.
// This function matches the arguments from the outer function to the inner enzyme call.
//
// This function also considers that Rust level arguments not always match the llvm-ir level
// arguments. A slice, `&[f32]`, for example, is represented as a pointer and a length on
// llvm-ir level. The number of activities matches the number of Rust level arguments, so we
// need to match those.
// FIXME(ZuseZ4): This logic is a bit more complicated than it should be, can we simplify it
// using iterators and peek()?
fn match_args_from_caller_to_enzyme<'ll>(
cx: &SimpleCx<'ll>,
width: u32,
args: &mut Vec<&'ll llvm::Value>,
inputs: &[DiffActivity],
outer_args: &[&'ll llvm::Value],
has_sret: bool,
) {
debug!("matching autodiff arguments");
// We now handle the issue that Rust level arguments not always match the llvm-ir level
@ -44,6 +67,14 @@ fn match_args_from_caller_to_enzyme<'ll>(
let mut outer_pos: usize = 0;
let mut activity_pos = 0;
if has_sret {
// Then the first outer arg is the sret pointer. Enzyme doesn't know about sret, so the
// inner function will still return something. We increase our outer_pos by one,
// and once we're done with all other args we will take the return of the inner call and
// update the sret pointer with it
outer_pos = 1;
}
let enzyme_const = cx.create_metadata("enzyme_const".to_string()).unwrap();
let enzyme_out = cx.create_metadata("enzyme_out".to_string()).unwrap();
let enzyme_dup = cx.create_metadata("enzyme_dup".to_string()).unwrap();
@ -92,23 +123,20 @@ fn match_args_from_caller_to_enzyme<'ll>(
// (..., metadata! enzyme_dup, ptr, ptr, int1, ...).
// FIXME(ZuseZ4): We will upstream a safety check later which asserts that
// int2 >= int1, which means the shadow vector is large enough to store the gradient.
assert!(unsafe {
llvm::LLVMRustGetTypeKind(next_outer_ty) == llvm::TypeKind::Integer
});
let next_outer_arg2 = outer_args[outer_pos + 2];
let next_outer_ty2 = cx.val_ty(next_outer_arg2);
assert!(unsafe {
llvm::LLVMRustGetTypeKind(next_outer_ty2) == llvm::TypeKind::Pointer
});
let next_outer_arg3 = outer_args[outer_pos + 3];
let next_outer_ty3 = cx.val_ty(next_outer_arg3);
assert!(unsafe {
llvm::LLVMRustGetTypeKind(next_outer_ty3) == llvm::TypeKind::Integer
});
args.push(next_outer_arg2);
assert_eq!(cx.type_kind(next_outer_ty), TypeKind::Integer);
for i in 0..(width as usize) {
let next_outer_arg2 = outer_args[outer_pos + 2 * (i + 1)];
let next_outer_ty2 = cx.val_ty(next_outer_arg2);
assert_eq!(cx.type_kind(next_outer_ty2), TypeKind::Pointer);
let next_outer_arg3 = outer_args[outer_pos + 2 * (i + 1) + 1];
let next_outer_ty3 = cx.val_ty(next_outer_arg3);
assert_eq!(cx.type_kind(next_outer_ty3), TypeKind::Integer);
args.push(next_outer_arg2);
}
args.push(cx.get_metadata_value(enzyme_const));
args.push(next_outer_arg);
outer_pos += 4;
outer_pos += 2 + 2 * width as usize;
activity_pos += 2;
} else {
// A duplicated pointer will have the following two outer_fn arguments:
@ -116,15 +144,19 @@ fn match_args_from_caller_to_enzyme<'ll>(
// (..., metadata! enzyme_dup, ptr, ptr, ...).
if matches!(diff_activity, DiffActivity::Duplicated | DiffActivity::DuplicatedOnly)
{
assert!(
unsafe { llvm::LLVMRustGetTypeKind(next_outer_ty) }
== llvm::TypeKind::Pointer
);
assert_eq!(cx.type_kind(next_outer_ty), TypeKind::Pointer);
}
// In the case of Dual we don't have assumptions, e.g. f32 would be valid.
args.push(next_outer_arg);
outer_pos += 2;
activity_pos += 1;
// Now, if width > 1, we need to account for that
for _ in 1..width {
let next_outer_arg = outer_args[outer_pos];
args.push(next_outer_arg);
outer_pos += 1;
}
}
} else {
// We do not differentiate with resprect to this argument.
@ -135,6 +167,76 @@ fn match_args_from_caller_to_enzyme<'ll>(
}
}
// On LLVM-IR, we can luckily declare __enzyme_ functions without specifying the input
// arguments. We do however need to declare them with their correct return type.
// We already figured the correct return type out in our frontend, when generating the outer_fn,
// so we can now just go ahead and use that. This is not always trivial, e.g. because sret.
// Beyond sret, this article describes our challenges nicely:
// <https://yorickpeterse.com/articles/the-mess-that-is-handling-structure-arguments-and-returns-in-llvm/>
// I.e. (i32, f32) will get merged into i64, but we don't handle that yet.
fn compute_enzyme_fn_ty<'ll>(
cx: &SimpleCx<'ll>,
attrs: &AutoDiffAttrs,
fn_to_diff: &'ll Value,
outer_fn: &'ll Value,
) -> &'ll llvm::Type {
let fn_ty = cx.get_type_of_global(outer_fn);
let mut ret_ty = cx.get_return_type(fn_ty);
let has_sret = has_sret(outer_fn);
if has_sret {
// Now we don't just forward the return type, so we have to figure it out based on the
// primal return type, in combination with the autodiff settings.
let fn_ty = cx.get_type_of_global(fn_to_diff);
let inner_ret_ty = cx.get_return_type(fn_ty);
let void_ty = unsafe { llvm::LLVMVoidTypeInContext(cx.llcx) };
if inner_ret_ty == void_ty {
// This indicates that even the inner function has an sret.
// Right now I only look for an sret in the outer function.
// This *probably* needs some extra handling, but I never ran
// into such a case. So I'll wait for user reports to have a test case.
bug!("sret in inner function");
}
if attrs.width == 1 {
todo!("Handle sret for scalar ad");
} else {
// First we check if we also have to deal with the primal return.
match attrs.mode {
DiffMode::Forward => match attrs.ret_activity {
DiffActivity::Dual => {
let arr_ty =
unsafe { llvm::LLVMArrayType2(inner_ret_ty, attrs.width as u64 + 1) };
ret_ty = arr_ty;
}
DiffActivity::DualOnly => {
let arr_ty =
unsafe { llvm::LLVMArrayType2(inner_ret_ty, attrs.width as u64) };
ret_ty = arr_ty;
}
DiffActivity::Const => {
todo!("Not sure, do we need to do something here?");
}
_ => {
bug!("unreachable");
}
},
DiffMode::Reverse => {
todo!("Handle sret for reverse mode");
}
_ => {
bug!("unreachable");
}
}
}
}
// LLVM can figure out the input types on it's own, so we take a shortcut here.
unsafe { llvm::LLVMFunctionType(ret_ty, ptr::null(), 0, True) }
}
/// When differentiating `fn_to_diff`, take a `outer_fn` and generate another
/// function with expected naming and calling conventions[^1] which will be
/// discovered by the enzyme LLVM pass and its body populated with the differentiated
@ -197,17 +299,9 @@ fn generate_enzyme_call<'ll>(
// }
// ```
unsafe {
// On LLVM-IR, we can luckily declare __enzyme_ functions without specifying the input
// arguments. We do however need to declare them with their correct return type.
// We already figured the correct return type out in our frontend, when generating the outer_fn,
// so we can now just go ahead and use that. FIXME(ZuseZ4): This doesn't handle sret yet.
let fn_ty = llvm::LLVMGlobalGetValueType(outer_fn);
let ret_ty = llvm::LLVMGetReturnType(fn_ty);
let enzyme_ty = compute_enzyme_fn_ty(cx, &attrs, fn_to_diff, outer_fn);
// LLVM can figure out the input types on it's own, so we take a shortcut here.
let enzyme_ty = llvm::LLVMFunctionType(ret_ty, ptr::null(), 0, True);
//FIXME(ZuseZ4): the CC/Addr/Vis values are best effort guesses, we should look at tests and
// FIXME(ZuseZ4): the CC/Addr/Vis values are best effort guesses, we should look at tests and
// think a bit more about what should go here.
let cc = llvm::LLVMGetFunctionCallConv(outer_fn);
let ad_fn = declare_simple_fn(
@ -240,14 +334,27 @@ fn generate_enzyme_call<'ll>(
if matches!(attrs.ret_activity, DiffActivity::Dual | DiffActivity::Active) {
args.push(cx.get_metadata_value(enzyme_primal_ret));
}
if attrs.width > 1 {
let enzyme_width = cx.create_metadata("enzyme_width".to_string()).unwrap();
args.push(cx.get_metadata_value(enzyme_width));
args.push(cx.get_const_i64(attrs.width as u64));
}
let has_sret = has_sret(outer_fn);
let outer_args: Vec<&llvm::Value> = get_params(outer_fn);
match_args_from_caller_to_enzyme(&cx, &mut args, &attrs.input_activity, &outer_args);
match_args_from_caller_to_enzyme(
&cx,
attrs.width,
&mut args,
&attrs.input_activity,
&outer_args,
has_sret,
);
let call = builder.call(enzyme_ty, ad_fn, &args, None);
// This part is a bit iffy. LLVM requires that a call to an inlineable function has some
// metadata attachted to it, but we just created this code oota. Given that the
// metadata attached to it, but we just created this code oota. Given that the
// differentiated function already has partly confusing metadata, and given that this
// affects nothing but the auttodiff IR, we take a shortcut and just steal metadata from the
// dummy code which we inserted at a higher level.
@ -268,7 +375,22 @@ fn generate_enzyme_call<'ll>(
// Now that we copied the metadata, get rid of dummy code.
llvm::LLVMRustEraseInstUntilInclusive(entry, last_inst);
if cx.val_ty(call) == cx.type_void() {
if cx.val_ty(call) == cx.type_void() || has_sret {
if has_sret {
// This is what we already have in our outer_fn (shortened):
// define void @_foo(ptr <..> sret([32 x i8]) initializes((0, 32)) %0, <...>) {
// %7 = call [4 x double] (...) @__enzyme_fwddiff_foo(ptr @square, metadata !"enzyme_width", i64 4, <...>)
// <Here we are, we want to add the following two lines>
// store [4 x double] %7, ptr %0, align 8
// ret void
// }
// now store the result of the enzyme call into the sret pointer.
let sret_ptr = outer_args[0];
let call_ty = cx.val_ty(call);
assert_eq!(cx.type_kind(call_ty), TypeKind::Array);
llvm::LLVMBuildStore(&builder.llbuilder, call, sret_ptr);
}
builder.ret_void();
} else {
builder.ret(call);
@ -300,8 +422,7 @@ pub(crate) fn differentiate<'ll>(
if !diff_items.is_empty()
&& !cgcx.opts.unstable_opts.autodiff.contains(&rustc_session::config::AutoDiff::Enable)
{
let dcx = cgcx.create_dcx();
return Err(dcx.handle().emit_almost_fatal(AutoDiffWithoutEnable));
return Err(diag_handler.handle().emit_almost_fatal(AutoDiffWithoutEnable));
}
// Before dumping the module, we want all the TypeTrees to become part of the module.

View file

@ -430,7 +430,7 @@ impl<'ll> CodegenCx<'ll, '_> {
let val_llty = self.val_ty(v);
let g = self.get_static_inner(def_id, val_llty);
let llty = llvm::LLVMGlobalGetValueType(g);
let llty = self.get_type_of_global(g);
let g = if val_llty == llty {
g

View file

@ -8,6 +8,7 @@ use std::str;
use rustc_abi::{HasDataLayout, Size, TargetDataLayout, VariantIdx};
use rustc_codegen_ssa::back::versioned_llvm_target;
use rustc_codegen_ssa::base::{wants_msvc_seh, wants_wasm_eh};
use rustc_codegen_ssa::common::TypeKind;
use rustc_codegen_ssa::errors as ssa_errors;
use rustc_codegen_ssa::traits::*;
use rustc_data_structures::base_n::{ALPHANUMERIC_ONLY, ToBaseN};
@ -38,7 +39,7 @@ use crate::debuginfo::metadata::apply_vcall_visibility_metadata;
use crate::llvm::Metadata;
use crate::type_::Type;
use crate::value::Value;
use crate::{attributes, coverageinfo, debuginfo, llvm, llvm_util};
use crate::{attributes, common, coverageinfo, debuginfo, llvm, llvm_util};
/// `TyCtxt` (and related cache datastructures) can't be move between threads.
/// However, there are various cx related functions which we want to be available to the builder and
@ -163,23 +164,6 @@ pub(crate) unsafe fn create_module<'ll>(
let mut target_data_layout = sess.target.data_layout.to_string();
let llvm_version = llvm_util::get_version();
if llvm_version < (19, 0, 0) {
if sess.target.arch == "aarch64" || sess.target.arch.starts_with("arm64") {
// LLVM 19 sets -Fn32 in its data layout string for 64-bit ARM
// Earlier LLVMs leave this default, so remove it.
// See https://github.com/llvm/llvm-project/pull/90702
target_data_layout = target_data_layout.replace("-Fn32", "");
}
}
if llvm_version < (19, 0, 0) {
if sess.target.arch == "loongarch64" {
// LLVM 19 updates the LoongArch64 data layout.
// See https://github.com/llvm/llvm-project/pull/93814
target_data_layout = target_data_layout.replace("-n32:64", "-n64");
}
}
if llvm_version < (20, 0, 0) {
if sess.target.arch == "aarch64" || sess.target.arch.starts_with("arm64") {
// LLVM 20 defines three additional address spaces for alternate
@ -327,6 +311,22 @@ pub(crate) unsafe fn create_module<'ll>(
pfe.prefix().into(),
);
}
// Add "kcfi-arity" module flag if KCFI arity indicator is enabled. (See
// https://github.com/llvm/llvm-project/pull/117121.)
if sess.is_sanitizer_kcfi_arity_enabled() {
// KCFI arity indicator requires LLVM 21.0.0 or later.
if llvm_version < (21, 0, 0) {
tcx.dcx().emit_err(crate::errors::SanitizerKcfiArityRequiresLLVM2100);
}
llvm::add_module_flag_u32(
llmod,
llvm::ModuleFlagMergeBehavior::Override,
"kcfi-arity",
1,
);
}
}
// Control Flow Guard is currently only supported by MSVC and LLVM on Windows.
@ -643,7 +643,18 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
llvm::set_section(g, c"llvm.metadata");
}
}
impl<'ll> SimpleCx<'ll> {
pub(crate) fn get_return_type(&self, ty: &'ll Type) -> &'ll Type {
assert_eq!(self.type_kind(ty), TypeKind::Function);
unsafe { llvm::LLVMGetReturnType(ty) }
}
pub(crate) fn get_type_of_global(&self, val: &'ll Value) -> &'ll Type {
unsafe { llvm::LLVMGlobalGetValueType(val) }
}
pub(crate) fn val_ty(&self, v: &'ll Value) -> &'ll Type {
common::val_ty(v)
}
}
impl<'ll> SimpleCx<'ll> {
pub(crate) fn new(
llmod: &'ll llvm::Module,
@ -660,6 +671,13 @@ impl<'ll, CX: Borrow<SCx<'ll>>> GenericCx<'ll, CX> {
llvm::LLVMMetadataAsValue(self.llcx(), metadata)
}
// FIXME(autodiff): We should split `ConstCodegenMethods` to pull the reusable parts
// onto a trait that is also implemented for GenericCx.
pub(crate) fn get_const_i64(&self, n: u64) -> &'ll Value {
let ty = unsafe { llvm::LLVMInt64TypeInContext(self.llcx()) };
unsafe { llvm::LLVMConstInt(ty, n, llvm::False) }
}
pub(crate) fn get_function(&self, name: &str) -> Option<&'ll Value> {
let name = SmallCStr::new(name);
unsafe { llvm::LLVMGetNamedFunction((**self).borrow().llmod, name.as_ptr()) }
@ -1183,10 +1201,8 @@ impl<'ll> CodegenCx<'ll, '_> {
if self.sess().instrument_coverage() {
ifn!("llvm.instrprof.increment", fn(ptr, t_i64, t_i32, t_i32) -> void);
if crate::llvm_util::get_version() >= (19, 0, 0) {
ifn!("llvm.instrprof.mcdc.parameters", fn(ptr, t_i64, t_i32) -> void);
ifn!("llvm.instrprof.mcdc.tvbitmap.update", fn(ptr, t_i64, t_i32, ptr) -> void);
}
ifn!("llvm.instrprof.mcdc.parameters", fn(ptr, t_i64, t_i32) -> void);
ifn!("llvm.instrprof.mcdc.tvbitmap.update", fn(ptr, t_i64, t_i32, ptr) -> void);
}
ifn!("llvm.type.test", fn(ptr, t_metadata) -> i1);

View file

@ -217,3 +217,7 @@ pub(crate) struct MismatchedDataLayout<'a> {
pub(crate) struct FixedX18InvalidArch<'a> {
pub arch: &'a str,
}
#[derive(Diagnostic)]
#[diag(codegen_llvm_sanitizer_kcfi_arity_requires_llvm_21_0_0)]
pub(crate) struct SanitizerKcfiArityRequiresLLVM2100;

View file

@ -4,7 +4,7 @@
use libc::{c_char, c_uint};
use super::MetadataKindId;
use super::ffi::{BasicBlock, Metadata, Module, Type, Value};
use super::ffi::{AttributeKind, BasicBlock, Metadata, Module, Type, Value};
use crate::llvm::Bool;
#[link(name = "llvm-wrapper", kind = "static")]
@ -17,6 +17,8 @@ unsafe extern "C" {
pub(crate) fn LLVMRustEraseInstFromParent(V: &Value);
pub(crate) fn LLVMRustGetTerminator<'a>(B: &BasicBlock) -> &'a Value;
pub(crate) fn LLVMRustVerifyFunction(V: &Value, action: LLVMRustVerifierFailureAction) -> Bool;
pub(crate) fn LLVMRustHasAttributeAtIndex(V: &Value, i: c_uint, Kind: AttributeKind) -> bool;
pub(crate) fn LLVMRustGetArrayNumElements(Ty: &Type) -> u64;
}
unsafe extern "C" {

View file

@ -1180,7 +1180,7 @@ unsafe extern "C" {
// Operations on parameters
pub(crate) fn LLVMIsAArgument(Val: &Value) -> Option<&Value>;
pub(crate) fn LLVMCountParams(Fn: &Value) -> c_uint;
pub(crate) safe fn LLVMCountParams(Fn: &Value) -> c_uint;
pub(crate) fn LLVMGetParam(Fn: &Value, Index: c_uint) -> &Value;
// Operations on basic blocks

View file

@ -256,7 +256,6 @@ pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option<LLVMFea
("aarch64", "pmuv3") => Some(LLVMFeature::new("perfmon")),
("aarch64", "paca") => Some(LLVMFeature::new("pauth")),
("aarch64", "pacg") => Some(LLVMFeature::new("pauth")),
("aarch64", "pauth-lr") if get_version().0 < 19 => None,
// Before LLVM 20 those two features were packaged together as b16b16
("aarch64", "sve-b16b16") if get_version().0 < 20 => Some(LLVMFeature::new("b16b16")),
("aarch64", "sme-b16b16") if get_version().0 < 20 => Some(LLVMFeature::new("b16b16")),
@ -270,20 +269,9 @@ pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option<LLVMFea
("aarch64", "fhm") => Some(LLVMFeature::new("fp16fml")),
("aarch64", "fp16") => Some(LLVMFeature::new("fullfp16")),
// Filter out features that are not supported by the current LLVM version
("aarch64", "fpmr") if get_version().0 != 18 => None,
("aarch64", "fpmr") => None, // only existed in 18
("arm", "fp16") => Some(LLVMFeature::new("fullfp16")),
// In LLVM 18, `unaligned-scalar-mem` was merged with `unaligned-vector-mem` into a single
// feature called `fast-unaligned-access`. In LLVM 19, it was split back out.
("riscv32" | "riscv64", "unaligned-scalar-mem" | "unaligned-vector-mem")
if get_version().0 == 18 =>
{
Some(LLVMFeature::new("fast-unaligned-access"))
}
// Filter out features that are not supported by the current LLVM version
("riscv32" | "riscv64", "zaamo") if get_version().0 < 19 => None,
("riscv32" | "riscv64", "zabha") if get_version().0 < 19 => None,
("riscv32" | "riscv64", "zalrsc") if get_version().0 < 19 => None,
("riscv32" | "riscv64", "zama16b") if get_version().0 < 19 => None,
("riscv32" | "riscv64", "zacas") if get_version().0 < 20 => None,
// Enable the evex512 target feature if an avx512 target feature is enabled.
("x86", s) if s.starts_with("avx512") => {
@ -295,10 +283,9 @@ pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option<LLVMFea
("sparc", "leoncasa") => Some(LLVMFeature::new("hasleoncasa")),
// In LLVM 19, there is no `v8plus` feature and `v9` means "SPARC-V9 instruction available and SPARC-V8+ ABI used".
// https://github.com/llvm/llvm-project/blob/llvmorg-19.1.0/llvm/lib/Target/Sparc/MCTargetDesc/SparcELFObjectWriter.cpp#L27-L28
// Before LLVM 19, there is no `v8plus` feature and `v9` means "SPARC-V9 instruction available".
// Before LLVM 19, there was no `v8plus` feature and `v9` means "SPARC-V9 instruction available".
// https://github.com/llvm/llvm-project/blob/llvmorg-18.1.0/llvm/lib/Target/Sparc/MCTargetDesc/SparcELFObjectWriter.cpp#L26
("sparc", "v8plus") if get_version().0 == 19 => Some(LLVMFeature::new("v9")),
("sparc", "v8plus") if get_version().0 < 19 => None,
("powerpc", "power8-crypto") => Some(LLVMFeature::new("crypto")),
// These new `amx` variants and `movrs` were introduced in LLVM20
("x86", "amx-avx512" | "amx-fp8" | "amx-movrs" | "amx-tf32" | "amx-transpose")

View file

@ -120,7 +120,7 @@ impl CodegenCx<'_, '_> {
}
// Match clang by only supporting COFF and ELF for now.
if self.tcx.sess.target.is_like_osx {
if self.tcx.sess.target.is_like_darwin {
return false;
}

View file

@ -399,7 +399,7 @@ pub(super) fn emit_va_arg<'ll, 'tcx>(
emit_ptr_va_arg(bx, addr, target_ty, false, Align::from_bytes(8).unwrap(), false)
}
// macOS / iOS AArch64
"aarch64" if target.is_like_osx => {
"aarch64" if target.is_like_darwin => {
emit_ptr_va_arg(bx, addr, target_ty, false, Align::from_bytes(8).unwrap(), true)
}
"aarch64" => emit_aapcs_va_arg(bx, addr, target_ty),

View file

@ -4,12 +4,6 @@ codegen_ssa_add_native_library = failed to add native library {$library_path}: {
codegen_ssa_aix_strip_not_used = using host's `strip` binary to cross-compile to AIX which is not guaranteed to work
codegen_ssa_apple_deployment_target_invalid =
failed to parse deployment target specified in {$env_var}: {$error}
codegen_ssa_apple_deployment_target_too_low =
deployment target in {$env_var} was set to {$version}, but the minimum supported by `rustc` is {$os_min}
codegen_ssa_archive_build_failure = failed to build archive at `{$path}`: {$error}
codegen_ssa_atomic_compare_exchange = Atomic compare-exchange intrinsic missing failure memory ordering

View file

@ -1,7 +1,4 @@
use std::env;
use std::ffi::OsString;
use std::fmt::{Display, from_fn};
use std::num::ParseIntError;
use std::path::PathBuf;
use std::process::Command;
@ -9,9 +6,10 @@ use itertools::Itertools;
use rustc_middle::middle::exported_symbols::SymbolExportKind;
use rustc_session::Session;
use rustc_target::spec::Target;
pub(super) use rustc_target::spec::apple::OSVersion;
use tracing::debug;
use crate::errors::{AppleDeploymentTarget, XcrunError, XcrunSdkPathWarning};
use crate::errors::{XcrunError, XcrunSdkPathWarning};
use crate::fluent_generated as fluent;
#[cfg(test)]
@ -134,124 +132,6 @@ pub(super) fn add_data_and_relocation(
Ok(())
}
/// Deployment target or SDK version.
///
/// The size of the numbers in here are limited by Mach-O's `LC_BUILD_VERSION`.
type OSVersion = (u16, u8, u8);
/// Parse an OS version triple (SDK version or deployment target).
fn parse_version(version: &str) -> Result<OSVersion, ParseIntError> {
if let Some((major, minor)) = version.split_once('.') {
let major = major.parse()?;
if let Some((minor, patch)) = minor.split_once('.') {
Ok((major, minor.parse()?, patch.parse()?))
} else {
Ok((major, minor.parse()?, 0))
}
} else {
Ok((version.parse()?, 0, 0))
}
}
pub fn pretty_version(version: OSVersion) -> impl Display {
let (major, minor, patch) = version;
from_fn(move |f| {
write!(f, "{major}.{minor}")?;
if patch != 0 {
write!(f, ".{patch}")?;
}
Ok(())
})
}
/// Minimum operating system versions currently supported by `rustc`.
fn os_minimum_deployment_target(os: &str) -> OSVersion {
// When bumping a version in here, remember to update the platform-support docs too.
//
// NOTE: The defaults may change in future `rustc` versions, so if you are looking for the
// default deployment target, prefer:
// ```
// $ rustc --print deployment-target
// ```
match os {
"macos" => (10, 12, 0),
"ios" => (10, 0, 0),
"tvos" => (10, 0, 0),
"watchos" => (5, 0, 0),
"visionos" => (1, 0, 0),
_ => unreachable!("tried to get deployment target for non-Apple platform"),
}
}
/// The deployment target for the given target.
///
/// This is similar to `os_minimum_deployment_target`, except that on certain targets it makes sense
/// to raise the minimum OS version.
///
/// This matches what LLVM does, see in part:
/// <https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/llvm/lib/TargetParser/Triple.cpp#L1900-L1932>
fn minimum_deployment_target(target: &Target) -> OSVersion {
match (&*target.os, &*target.arch, &*target.abi) {
("macos", "aarch64", _) => (11, 0, 0),
("ios", "aarch64", "macabi") => (14, 0, 0),
("ios", "aarch64", "sim") => (14, 0, 0),
("ios", _, _) if target.llvm_target.starts_with("arm64e") => (14, 0, 0),
// Mac Catalyst defaults to 13.1 in Clang.
("ios", _, "macabi") => (13, 1, 0),
("tvos", "aarch64", "sim") => (14, 0, 0),
("watchos", "aarch64", "sim") => (7, 0, 0),
(os, _, _) => os_minimum_deployment_target(os),
}
}
/// Name of the environment variable used to fetch the deployment target on the given OS.
pub fn deployment_target_env_var(os: &str) -> &'static str {
match os {
"macos" => "MACOSX_DEPLOYMENT_TARGET",
"ios" => "IPHONEOS_DEPLOYMENT_TARGET",
"watchos" => "WATCHOS_DEPLOYMENT_TARGET",
"tvos" => "TVOS_DEPLOYMENT_TARGET",
"visionos" => "XROS_DEPLOYMENT_TARGET",
_ => unreachable!("tried to get deployment target env var for non-Apple platform"),
}
}
/// Get the deployment target based on the standard environment variables, or fall back to the
/// minimum version supported by `rustc`.
pub fn deployment_target(sess: &Session) -> OSVersion {
let min = minimum_deployment_target(&sess.target);
let env_var = deployment_target_env_var(&sess.target.os);
if let Ok(deployment_target) = env::var(env_var) {
match parse_version(&deployment_target) {
Ok(version) => {
let os_min = os_minimum_deployment_target(&sess.target.os);
// It is common that the deployment target is set a bit too low, for example on
// macOS Aarch64 to also target older x86_64. So we only want to warn when variable
// is lower than the minimum OS supported by rustc, not when the variable is lower
// than the minimum for a specific target.
if version < os_min {
sess.dcx().emit_warn(AppleDeploymentTarget::TooLow {
env_var,
version: pretty_version(version).to_string(),
os_min: pretty_version(os_min).to_string(),
});
}
// Raise the deployment target to the minimum supported.
version.max(min)
}
Err(error) => {
sess.dcx().emit_err(AppleDeploymentTarget::Invalid { env_var, error });
min
}
}
} else {
// If no deployment target variable is set, default to the minimum found above.
min
}
}
pub(super) fn add_version_to_llvm_target(
llvm_target: &str,
deployment_target: OSVersion,
@ -263,18 +143,17 @@ pub(super) fn add_version_to_llvm_target(
let environment = components.next();
assert_eq!(components.next(), None, "too many LLVM triple components");
let (major, minor, patch) = deployment_target;
assert!(
!os.contains(|c: char| c.is_ascii_digit()),
"LLVM target must not already be versioned"
);
let version = deployment_target.fmt_full();
if let Some(env) = environment {
// Insert version into OS, before environment
format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}-{env}")
format!("{arch}-{vendor}-{os}{version}-{env}")
} else {
format!("{arch}-{vendor}-{os}{major}.{minor}.{patch}")
format!("{arch}-{vendor}-{os}{version}")
}
}

View file

@ -3,23 +3,15 @@ use super::*;
#[test]
fn test_add_version_to_llvm_target() {
assert_eq!(
add_version_to_llvm_target("aarch64-apple-macosx", (10, 14, 1)),
add_version_to_llvm_target("aarch64-apple-macosx", OSVersion::new(10, 14, 1)),
"aarch64-apple-macosx10.14.1"
);
assert_eq!(
add_version_to_llvm_target("aarch64-apple-ios-simulator", (16, 1, 0)),
add_version_to_llvm_target("aarch64-apple-ios-simulator", OSVersion::new(16, 1, 0)),
"aarch64-apple-ios16.1.0-simulator"
);
}
#[test]
fn test_parse_version() {
assert_eq!(parse_version("10"), Ok((10, 0, 0)));
assert_eq!(parse_version("10.12"), Ok((10, 12, 0)));
assert_eq!(parse_version("10.12.6"), Ok((10, 12, 6)));
assert_eq!(parse_version("9999.99.99"), Ok((9999, 99, 99)));
}
#[test]
#[cfg_attr(not(target_os = "macos"), ignore = "xcode-select is only available on macOS")]
fn lookup_developer_dir() {

View file

@ -959,9 +959,9 @@ fn link_natively(
}
}
let (level, src) = codegen_results.crate_info.lint_levels.linker_messages;
let level = codegen_results.crate_info.lint_levels.linker_messages;
let lint = |msg| {
lint_level(sess, LINKER_MESSAGES, level, src, None, |diag| {
lint_level(sess, LINKER_MESSAGES, level, None, |diag| {
LinkerOutput { inner: msg }.decorate_lint(diag)
})
};
@ -1012,7 +1012,7 @@ fn link_natively(
// On macOS the external `dsymutil` tool is used to create the packed
// debug information. Note that this will read debug information from
// the objects on the filesystem which we'll clean up later.
SplitDebuginfo::Packed if sess.target.is_like_osx => {
SplitDebuginfo::Packed if sess.target.is_like_darwin => {
let prog = Command::new("dsymutil").arg(out_filename).output();
match prog {
Ok(prog) => {
@ -1043,7 +1043,7 @@ fn link_natively(
let strip = sess.opts.cg.strip;
if sess.target.is_like_osx {
if sess.target.is_like_darwin {
let stripcmd = "rust-objcopy";
match (strip, crate_type) {
(Strip::Debuginfo, _) => {
@ -1241,7 +1241,7 @@ fn add_sanitizer_libraries(
// Everywhere else the runtimes are currently distributed as static
// libraries which should be linked to executables only.
if matches!(crate_type, CrateType::Dylib | CrateType::Cdylib | CrateType::ProcMacro)
&& !(sess.target.is_like_osx || sess.target.is_like_msvc)
&& !(sess.target.is_like_darwin || sess.target.is_like_msvc)
{
return;
}
@ -1294,7 +1294,7 @@ fn link_sanitizer_runtime(
let channel =
option_env!("CFG_RELEASE_CHANNEL").map(|channel| format!("-{channel}")).unwrap_or_default();
if sess.target.is_like_osx {
if sess.target.is_like_darwin {
// On Apple platforms, the sanitizer is always built as a dylib, and
// LLVM will link to `@rpath/*.dylib`, so we need to specify an
// rpath to the library as well (the rpath should be absolute, see
@ -2182,7 +2182,7 @@ fn add_rpath_args(
let rpath_config = RPathConfig {
libs: &*libs,
out_filename: out_filename.to_path_buf(),
is_like_osx: sess.target.is_like_osx,
is_like_darwin: sess.target.is_like_darwin,
linker_is_gnu: sess.target.linker_flavor.is_gnu(),
};
cmd.link_args(&rpath::get_rpath_linker_args(&rpath_config));
@ -3044,7 +3044,7 @@ pub(crate) fn are_upstream_rust_objects_already_included(sess: &Session) -> bool
/// - The deployment target.
/// - The SDK version.
fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) {
if !sess.target.is_like_osx {
if !sess.target.is_like_darwin {
return;
}
let LinkerFlavor::Darwin(cc, _) = flavor else {
@ -3115,8 +3115,7 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo
_ => bug!("invalid OS/ABI combination for Apple target: {target_os}, {target_abi}"),
};
let (major, minor, patch) = apple::deployment_target(sess);
let min_version = format!("{major}.{minor}.{patch}");
let min_version = sess.apple_deployment_target().fmt_full().to_string();
// The SDK version is used at runtime when compiling with a newer SDK / version of Xcode:
// - By dyld to give extra warnings and errors, see e.g.:
@ -3185,10 +3184,10 @@ fn add_apple_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavo
// The presence of `-mmacosx-version-min` makes CC default to
// macOS, and it sets the deployment target.
let (major, minor, patch) = apple::deployment_target(sess);
let version = sess.apple_deployment_target().fmt_full();
// Intentionally pass this as a single argument, Clang doesn't
// seem to like it otherwise.
cmd.cc_arg(&format!("-mmacosx-version-min={major}.{minor}.{patch}"));
cmd.cc_arg(&format!("-mmacosx-version-min={version}"));
// macOS has no environment, so with these two, we've told CC the
// four desired parameters.

View file

@ -373,7 +373,7 @@ impl<'a> GccLinker<'a> {
// * On OSX they have their own linker, not binutils'
// * For WebAssembly the only functional linker is LLD, which doesn't
// support hint flags
!self.sess.target.is_like_osx && !self.sess.target.is_like_wasm
!self.sess.target.is_like_darwin && !self.sess.target.is_like_wasm
}
// Some platforms take hints about whether a library is static or dynamic.
@ -425,7 +425,7 @@ impl<'a> GccLinker<'a> {
fn build_dylib(&mut self, crate_type: CrateType, out_filename: &Path) {
// On mac we need to tell the linker to let this library be rpathed
if self.sess.target.is_like_osx {
if self.sess.target.is_like_darwin {
if self.is_cc() {
// `-dynamiclib` makes `cc` pass `-dylib` to the linker.
self.cc_arg("-dynamiclib");
@ -471,7 +471,7 @@ impl<'a> GccLinker<'a> {
fn with_as_needed(&mut self, as_needed: bool, f: impl FnOnce(&mut Self)) {
if !as_needed {
if self.sess.target.is_like_osx {
if self.sess.target.is_like_darwin {
// FIXME(81490): ld64 doesn't support these flags but macOS 11
// has -needed-l{} / -needed_library {}
// but we have no way to detect that here.
@ -486,7 +486,7 @@ impl<'a> GccLinker<'a> {
f(self);
if !as_needed {
if self.sess.target.is_like_osx {
if self.sess.target.is_like_darwin {
// See above FIXME comment
} else if self.is_gnu && !self.sess.target.is_like_windows {
self.link_arg("--as-needed");
@ -619,7 +619,7 @@ impl<'a> Linker for GccLinker<'a> {
let colon = if verbatim && self.is_gnu { ":" } else { "" };
if !whole_archive {
self.link_or_cc_arg(format!("-l{colon}{name}"));
} else if self.sess.target.is_like_osx {
} else if self.sess.target.is_like_darwin {
// -force_load is the macOS equivalent of --whole-archive, but it
// involves passing the full path to the library to link.
self.link_arg("-force_load");
@ -635,7 +635,7 @@ impl<'a> Linker for GccLinker<'a> {
self.hint_static();
if !whole_archive {
self.link_or_cc_arg(path);
} else if self.sess.target.is_like_osx {
} else if self.sess.target.is_like_darwin {
self.link_arg("-force_load").link_arg(path);
} else {
self.link_arg("--whole-archive").link_arg(path).link_arg("--no-whole-archive");
@ -670,7 +670,7 @@ impl<'a> Linker for GccLinker<'a> {
// -dead_strip can't be part of the pre_link_args because it's also used
// for partial linking when using multiple codegen units (-r). So we
// insert it here.
if self.sess.target.is_like_osx {
if self.sess.target.is_like_darwin {
self.link_arg("-dead_strip");
// If we're building a dylib, we don't use --gc-sections because LLVM
@ -728,7 +728,7 @@ impl<'a> Linker for GccLinker<'a> {
fn debuginfo(&mut self, strip: Strip, _: &[PathBuf]) {
// MacOS linker doesn't support stripping symbols directly anymore.
if self.sess.target.is_like_osx {
if self.sess.target.is_like_darwin {
return;
}
@ -795,7 +795,7 @@ impl<'a> Linker for GccLinker<'a> {
debug!("EXPORTED SYMBOLS:");
if self.sess.target.is_like_osx {
if self.sess.target.is_like_darwin {
// Write a plain, newline-separated list of symbols
let res: io::Result<()> = try {
let mut f = File::create_buffered(&path)?;
@ -841,7 +841,7 @@ impl<'a> Linker for GccLinker<'a> {
}
}
if self.sess.target.is_like_osx {
if self.sess.target.is_like_darwin {
self.link_arg("-exported_symbols_list").link_arg(path);
} else if self.sess.target.is_like_solaris {
self.link_arg("-M").link_arg(path);

View file

@ -214,7 +214,7 @@ pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static
let mut file = write::Object::new(binary_format, architecture, endianness);
file.set_sub_architecture(sub_architecture);
if sess.target.is_like_osx {
if sess.target.is_like_darwin {
if macho_is_arm64e(&sess.target) {
file.set_macho_cpu_subtype(object::macho::CPU_SUBTYPE_ARM64E);
}
@ -388,13 +388,13 @@ pub(super) fn elf_e_flags(architecture: Architecture, sess: &Session) -> u32 {
fn macho_object_build_version_for_target(sess: &Session) -> object::write::MachOBuildVersion {
/// The `object` crate demands "X.Y.Z encoded in nibbles as xxxx.yy.zz"
/// e.g. minOS 14.0 = 0x000E0000, or SDK 16.2 = 0x00100200
fn pack_version((major, minor, patch): (u16, u8, u8)) -> u32 {
fn pack_version(apple::OSVersion { major, minor, patch }: apple::OSVersion) -> u32 {
let (major, minor, patch) = (major as u32, minor as u32, patch as u32);
(major << 16) | (minor << 8) | patch
}
let platform = apple::macho_platform(&sess.target);
let min_os = apple::deployment_target(sess);
let min_os = sess.apple_deployment_target();
let mut build_version = object::write::MachOBuildVersion::default();
build_version.platform = platform;

View file

@ -19,8 +19,8 @@ pub mod write;
///
/// Certain optimizations also depend on the deployment target.
pub fn versioned_llvm_target(sess: &Session) -> Cow<'_, str> {
if sess.target.is_like_osx {
apple::add_version_to_llvm_target(&sess.target.llvm_target, apple::deployment_target(sess))
if sess.target.is_like_darwin {
apple::add_version_to_llvm_target(&sess.target.llvm_target, sess.apple_deployment_target())
.into()
} else {
// FIXME(madsmtm): Certain other targets also include a version,

View file

@ -9,7 +9,7 @@ use tracing::debug;
pub(super) struct RPathConfig<'a> {
pub libs: &'a [&'a Path],
pub out_filename: PathBuf,
pub is_like_osx: bool,
pub is_like_darwin: bool,
pub linker_is_gnu: bool,
}
@ -63,7 +63,7 @@ fn get_rpaths_relative_to_output(config: &RPathConfig<'_>) -> Vec<OsString> {
fn get_rpath_relative_to_output(config: &RPathConfig<'_>, lib: &Path) -> OsString {
// Mac doesn't appear to support $ORIGIN
let prefix = if config.is_like_osx { "@loader_path" } else { "$ORIGIN" };
let prefix = if config.is_like_darwin { "@loader_path" } else { "$ORIGIN" };
// Strip filenames
let lib = lib.parent().unwrap();

View file

@ -28,7 +28,7 @@ fn test_rpath_relative() {
if cfg!(target_os = "macos") {
let config = &mut RPathConfig {
libs: &[],
is_like_osx: true,
is_like_darwin: true,
linker_is_gnu: false,
out_filename: PathBuf::from("bin/rustc"),
};
@ -38,7 +38,7 @@ fn test_rpath_relative() {
let config = &mut RPathConfig {
libs: &[],
out_filename: PathBuf::from("bin/rustc"),
is_like_osx: false,
is_like_darwin: false,
linker_is_gnu: true,
};
let res = get_rpath_relative_to_output(config, Path::new("lib/libstd.so"));
@ -51,7 +51,7 @@ fn test_rpath_relative_issue_119571() {
let config = &mut RPathConfig {
libs: &[],
out_filename: PathBuf::from("rustc"),
is_like_osx: false,
is_like_darwin: false,
linker_is_gnu: true,
};
// Should not panic when out_filename only contains filename.

View file

@ -352,7 +352,7 @@ pub struct CodegenContext<B: WriteBackendMethods> {
pub is_pe_coff: bool,
pub target_can_use_split_dwarf: bool,
pub target_arch: String,
pub target_is_like_osx: bool,
pub target_is_like_darwin: bool,
pub target_is_like_aix: bool,
pub split_debuginfo: rustc_target::spec::SplitDebuginfo,
pub split_dwarf_kind: rustc_session::config::SplitDwarfKind,
@ -1216,7 +1216,7 @@ fn start_executing_work<B: ExtraBackendMethods>(
is_pe_coff: tcx.sess.target.is_like_windows,
target_can_use_split_dwarf: tcx.sess.target_can_use_split_dwarf(),
target_arch: tcx.sess.target.arch.to_string(),
target_is_like_osx: tcx.sess.target.is_like_osx,
target_is_like_darwin: tcx.sess.target.is_like_darwin,
target_is_like_aix: tcx.sess.target.is_like_aix,
split_debuginfo: tcx.sess.split_debuginfo(),
split_dwarf_kind: tcx.sess.opts.unstable_opts.split_dwarf_kind,

View file

@ -2,7 +2,7 @@ use std::str::FromStr;
use rustc_abi::ExternAbi;
use rustc_ast::expand::autodiff_attrs::{AutoDiffAttrs, DiffActivity, DiffMode};
use rustc_ast::{MetaItem, MetaItemInner, attr};
use rustc_ast::{LitKind, MetaItem, MetaItemInner, attr};
use rustc_attr_parsing::ReprAttr::ReprAlign;
use rustc_attr_parsing::{AttributeKind, InlineAttr, InstructionSetAttr, OptimizeAttr};
use rustc_data_structures::fx::FxHashMap;
@ -213,7 +213,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
// somewhat, and is subject to change in the future (which
// is a good thing, because this would ideally be a bit
// more firmed up).
let is_like_elf = !(tcx.sess.target.is_like_osx
let is_like_elf = !(tcx.sess.target.is_like_darwin
|| tcx.sess.target.is_like_windows
|| tcx.sess.target.is_like_wasm);
codegen_fn_attrs.flags |= if is_like_elf {
@ -805,8 +805,8 @@ fn autodiff_attrs(tcx: TyCtxt<'_>, id: DefId) -> Option<AutoDiffAttrs> {
return Some(AutoDiffAttrs::source());
}
let [mode, input_activities @ .., ret_activity] = &list[..] else {
span_bug!(attr.span(), "rustc_autodiff attribute must contain mode and activities");
let [mode, width_meta, input_activities @ .., ret_activity] = &list[..] else {
span_bug!(attr.span(), "rustc_autodiff attribute must contain mode, width and activities");
};
let mode = if let MetaItemInner::MetaItem(MetaItem { path: p1, .. }) = mode {
p1.segments.first().unwrap().ident
@ -823,6 +823,30 @@ fn autodiff_attrs(tcx: TyCtxt<'_>, id: DefId) -> Option<AutoDiffAttrs> {
}
};
let width: u32 = match width_meta {
MetaItemInner::MetaItem(MetaItem { path: p1, .. }) => {
let w = p1.segments.first().unwrap().ident;
match w.as_str().parse() {
Ok(val) => val,
Err(_) => {
span_bug!(w.span, "rustc_autodiff width should fit u32");
}
}
}
MetaItemInner::Lit(lit) => {
if let LitKind::Int(val, _) = lit.kind {
match val.get().try_into() {
Ok(val) => val,
Err(_) => {
span_bug!(lit.span, "rustc_autodiff width should fit u32");
}
}
} else {
span_bug!(lit.span, "rustc_autodiff width should be an integer");
}
}
};
// First read the ret symbol from the attribute
let ret_symbol = if let MetaItemInner::MetaItem(MetaItem { path: p1, .. }) = ret_activity {
p1.segments.first().unwrap().ident
@ -860,7 +884,7 @@ fn autodiff_attrs(tcx: TyCtxt<'_>, id: DefId) -> Option<AutoDiffAttrs> {
}
}
Some(AutoDiffAttrs { mode, ret_activity, input_activity: arg_activities })
Some(AutoDiffAttrs { mode, width, ret_activity, input_activity: arg_activities })
}
pub(crate) fn provide(providers: &mut Providers) {

View file

@ -3,7 +3,6 @@
use std::borrow::Cow;
use std::ffi::OsString;
use std::io::Error;
use std::num::ParseIntError;
use std::path::{Path, PathBuf};
use std::process::ExitStatus;
@ -738,14 +737,6 @@ pub enum ExtractBundledLibsError<'a> {
ExtractSection { rlib: &'a Path, error: Box<dyn std::error::Error> },
}
#[derive(Diagnostic)]
pub(crate) enum AppleDeploymentTarget {
#[diag(codegen_ssa_apple_deployment_target_invalid)]
Invalid { env_var: &'static str, error: ParseIntError },
#[diag(codegen_ssa_apple_deployment_target_too_low)]
TooLow { env_var: &'static str, version: String, os_min: String },
}
#[derive(Diagnostic)]
#[diag(codegen_ssa_read_file)]
pub(crate) struct ReadFileError {

View file

@ -7,7 +7,6 @@
#![doc(rust_logo)]
#![feature(assert_matches)]
#![feature(box_patterns)]
#![feature(debug_closure_helpers)]
#![feature(file_buffered)]
#![feature(if_let_guard)]
#![feature(let_chains)]
@ -34,7 +33,7 @@ use rustc_hir::CRATE_HIR_ID;
use rustc_hir::def_id::CrateNum;
use rustc_macros::{Decodable, Encodable, HashStable};
use rustc_middle::dep_graph::WorkProduct;
use rustc_middle::lint::LintLevelSource;
use rustc_middle::lint::LevelAndSource;
use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile;
use rustc_middle::middle::dependency_format::Dependencies;
use rustc_middle::middle::exported_symbols::SymbolExportKind;
@ -45,7 +44,6 @@ use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
use rustc_session::Session;
use rustc_session::config::{CrateType, OutputFilenames, OutputType, RUST_CGU_EXT};
use rustc_session::cstore::{self, CrateSource};
use rustc_session::lint::Level;
use rustc_session::lint::builtin::LINKER_MESSAGES;
use rustc_session::utils::NativeLibKind;
use rustc_span::Symbol;
@ -341,7 +339,7 @@ impl CodegenResults {
/// Instead, encode exactly the information we need.
#[derive(Copy, Clone, Debug, Encodable, Decodable)]
pub struct CodegenLintLevels {
linker_messages: (Level, LintLevelSource),
linker_messages: LevelAndSource,
}
impl CodegenLintLevels {

View file

@ -335,7 +335,7 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
self.tcx.dcx().span_bug(span, "tls access is checked in `Rvalue::ThreadLocalRef`");
}
if let Some(def_id) = def_id.as_local()
&& let Err(guar) = self.tcx.at(span).check_well_formed(hir::OwnerId { def_id })
&& let Err(guar) = self.tcx.ensure_ok().check_well_formed(hir::OwnerId { def_id })
{
self.error_emitted = Some(guar);
}

View file

@ -546,7 +546,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
rustc_session::lint::builtin::LONG_RUNNING_CONST_EVAL,
hir_id,
)
.0
.level
.is_error();
let span = ecx.cur_span();
ecx.tcx.emit_node_span_lint(

View file

@ -9,6 +9,8 @@ pub type FxIndexSet<V> = indexmap::IndexSet<V, BuildHasherDefault<FxHasher>>;
pub type IndexEntry<'a, K, V> = indexmap::map::Entry<'a, K, V>;
pub type IndexOccupiedEntry<'a, K, V> = indexmap::map::OccupiedEntry<'a, K, V>;
pub use indexmap::set::MutableValues;
#[macro_export]
macro_rules! define_id_collections {
($map_name:ident, $set_name:ident, $entry_name:ident, $key:ty) => {

View file

@ -34,7 +34,6 @@ use std::time::{Instant, SystemTime};
use std::{env, str};
use rustc_ast as ast;
use rustc_codegen_ssa::back::apple;
use rustc_codegen_ssa::traits::CodegenBackend;
use rustc_codegen_ssa::{CodegenErrors, CodegenResults};
use rustc_data_structures::profiling::{
@ -64,6 +63,7 @@ use rustc_session::lint::{Lint, LintId};
use rustc_session::output::{CRATE_TYPES, collect_crate_types, invalid_output_for_target};
use rustc_session::{EarlyDiagCtxt, Session, config, filesearch};
use rustc_span::FileName;
use rustc_span::def_id::LOCAL_CRATE;
use rustc_target::json::ToJson;
use rustc_target::spec::{Target, TargetTuple};
use time::OffsetDateTime;
@ -392,14 +392,10 @@ pub fn run_compiler(at_args: &[String], callbacks: &mut (dyn Callbacks + Send))
}
fn dump_feature_usage_metrics(tcxt: TyCtxt<'_>, metrics_dir: &Path) {
let output_filenames = tcxt.output_filenames(());
let mut metrics_file_name = std::ffi::OsString::from("unstable_feature_usage_metrics-");
let mut metrics_path = output_filenames.with_directory_and_extension(metrics_dir, "json");
let metrics_file_stem =
metrics_path.file_name().expect("there should be a valid default output filename");
metrics_file_name.push(metrics_file_stem);
metrics_path.pop();
metrics_path.push(metrics_file_name);
let hash = tcxt.crate_hash(LOCAL_CRATE);
let crate_name = tcxt.crate_name(LOCAL_CRATE);
let metrics_file_name = format!("unstable_feature_usage_metrics-{crate_name}-{hash}.json");
let metrics_path = metrics_dir.join(metrics_file_name);
if let Err(error) = tcxt.features().dump_feature_usage_metrics(metrics_path) {
// FIXME(yaahc): once metrics can be enabled by default we will want "failure to emit
// default metrics" to only produce a warning when metrics are enabled by default and emit
@ -715,7 +711,7 @@ fn print_crate_info(
// lint is unstable and feature gate isn't active, don't print
continue;
}
let level = lint_levels.lint_level(lint).0;
let level = lint_levels.lint_level(lint).level;
println_info!("{}={}", lint.name_lower(), level.as_str());
}
}
@ -807,11 +803,11 @@ fn print_crate_info(
}
}
DeploymentTarget => {
if sess.target.is_like_osx {
if sess.target.is_like_darwin {
println_info!(
"{}={}",
apple::deployment_target_env_var(&sess.target.os),
apple::pretty_version(apple::deployment_target(sess)),
rustc_target::spec::apple::deployment_target_env_var(&sess.target.os),
sess.apple_deployment_target().fmt_pretty(),
)
} else {
#[allow(rustc::diagnostic_outside_of_impl)]

View file

@ -91,13 +91,13 @@ fn annotation_level_for_level(level: Level) -> annotate_snippets::Level {
Level::Bug | Level::Fatal | Level::Error | Level::DelayedBug => {
annotate_snippets::Level::Error
}
Level::ForceWarning(_) | Level::Warning => annotate_snippets::Level::Warning,
Level::ForceWarning | Level::Warning => annotate_snippets::Level::Warning,
Level::Note | Level::OnceNote => annotate_snippets::Level::Note,
Level::Help | Level::OnceHelp => annotate_snippets::Level::Help,
// FIXME(#59346): Not sure how to map this level
Level::FailureNote => annotate_snippets::Level::Error,
Level::Allow => panic!("Should not call with Allow"),
Level::Expect(_) => panic!("Should not call with Expect"),
Level::Expect => panic!("Should not call with Expect"),
}
}

View file

@ -9,7 +9,7 @@ use std::thread::panicking;
use rustc_data_structures::fx::FxIndexMap;
use rustc_error_messages::{FluentValue, fluent_value_from_str_list_sep_by_and};
use rustc_lint_defs::Applicability;
use rustc_lint_defs::{Applicability, LintExpectationId};
use rustc_macros::{Decodable, Encodable};
use rustc_span::source_map::Spanned;
use rustc_span::{DUMMY_SP, Span, Symbol};
@ -296,6 +296,7 @@ pub struct DiagInner {
pub messages: Vec<(DiagMessage, Style)>,
pub code: Option<ErrCode>,
pub lint_id: Option<LintExpectationId>,
pub span: MultiSpan,
pub children: Vec<Subdiag>,
pub suggestions: Suggestions,
@ -324,6 +325,7 @@ impl DiagInner {
pub fn new_with_messages(level: Level, messages: Vec<(DiagMessage, Style)>) -> Self {
DiagInner {
level,
lint_id: None,
messages,
code: None,
span: MultiSpan::new(),
@ -346,7 +348,7 @@ impl DiagInner {
match self.level {
Level::Bug | Level::Fatal | Level::Error | Level::DelayedBug => true,
Level::ForceWarning(_)
Level::ForceWarning
| Level::Warning
| Level::Note
| Level::OnceNote
@ -354,7 +356,7 @@ impl DiagInner {
| Level::OnceHelp
| Level::FailureNote
| Level::Allow
| Level::Expect(_) => false,
| Level::Expect => false,
}
}
@ -365,7 +367,7 @@ impl DiagInner {
pub(crate) fn is_force_warn(&self) -> bool {
match self.level {
Level::ForceWarning(_) => {
Level::ForceWarning => {
assert!(self.is_lint.is_some());
true
}
@ -1259,6 +1261,17 @@ impl<'a, G: EmissionGuarantee> Diag<'a, G> {
self
} }
with_fn! { with_lint_id,
/// Add an argument.
#[rustc_lint_diagnostics]
pub fn lint_id(
&mut self,
id: LintExpectationId,
) -> &mut Self {
self.lint_id = Some(id);
self
} }
with_fn! { with_primary_message,
/// Add a primary message.
#[rustc_lint_diagnostics]

View file

@ -144,7 +144,7 @@ impl Emitter for JsonEmitter {
//
// So to avoid ICEs and confused users we "upgrade" the lint level for
// those `FutureBreakageItem` to warn.
if matches!(diag.level, crate::Level::Allow | crate::Level::Expect(..)) {
if matches!(diag.level, crate::Level::Allow | crate::Level::Expect) {
diag.level = crate::Level::Warning;
}
FutureBreakageItem {

View file

@ -905,8 +905,8 @@ impl<'a> DiagCtxtHandle<'a> {
DelayedBug => {
return self.inner.borrow_mut().emit_diagnostic(diag, self.tainted_with_errors);
}
ForceWarning(_) | Warning | Note | OnceNote | Help | OnceHelp | FailureNote | Allow
| Expect(_) => None,
ForceWarning | Warning | Note | OnceNote | Help | OnceHelp | FailureNote | Allow
| Expect => None,
};
// FIXME(Centril, #69537): Consider reintroducing panic on overwriting a stashed diagnostic
@ -1045,7 +1045,7 @@ impl<'a> DiagCtxtHandle<'a> {
// Use `ForceWarning` rather than `Warning` to guarantee emission, e.g. with a
// configuration like `--cap-lints allow --force-warn bare_trait_objects`.
inner.emit_diagnostic(
DiagInner::new(ForceWarning(None), DiagMessage::Str(warnings)),
DiagInner::new(ForceWarning, DiagMessage::Str(warnings)),
None,
);
}
@ -1450,7 +1450,7 @@ impl<'a> DiagCtxtHandle<'a> {
#[rustc_lint_diagnostics]
#[track_caller]
pub fn struct_expect(self, msg: impl Into<DiagMessage>, id: LintExpectationId) -> Diag<'a, ()> {
Diag::new(self, Expect(id), msg)
Diag::new(self, Expect, msg).with_lint_id(id)
}
}
@ -1510,7 +1510,7 @@ impl DiagCtxtInner {
// Future breakages aren't emitted if they're `Level::Allow` or
// `Level::Expect`, but they still need to be constructed and
// stashed below, so they'll trigger the must_produce_diag check.
assert_matches!(diagnostic.level, Error | Warning | Allow | Expect(_));
assert_matches!(diagnostic.level, Error | Warning | Allow | Expect);
self.future_breakage_diagnostics.push(diagnostic.clone());
}
@ -1558,7 +1558,7 @@ impl DiagCtxtInner {
};
}
}
ForceWarning(None) => {} // `ForceWarning(Some(...))` is below, with `Expect`
ForceWarning if diagnostic.lint_id.is_none() => {} // `ForceWarning(Some(...))` is below, with `Expect`
Warning => {
if !self.flags.can_emit_warnings {
// We are not emitting warnings.
@ -1580,9 +1580,9 @@ impl DiagCtxtInner {
}
return None;
}
Expect(expect_id) | ForceWarning(Some(expect_id)) => {
self.fulfilled_expectations.insert(expect_id);
if let Expect(_) = diagnostic.level {
Expect | ForceWarning => {
self.fulfilled_expectations.insert(diagnostic.lint_id.unwrap());
if let Expect = diagnostic.level {
// Nothing emitted here for expected lints.
TRACK_DIAGNOSTIC(diagnostic, &mut |_| None);
self.suppressed_expected_diag = true;
@ -1631,7 +1631,7 @@ impl DiagCtxtInner {
if is_error {
self.deduplicated_err_count += 1;
} else if matches!(diagnostic.level, ForceWarning(_) | Warning) {
} else if matches!(diagnostic.level, ForceWarning | Warning) {
self.deduplicated_warn_count += 1;
}
self.has_printed = true;
@ -1899,9 +1899,9 @@ pub enum Level {
/// A `force-warn` lint warning about the code being compiled. Does not prevent compilation
/// from finishing.
///
/// The [`LintExpectationId`] is used for expected lint diagnostics. In all other cases this
/// Requires a [`LintExpectationId`] for expected lint diagnostics. In all other cases this
/// should be `None`.
ForceWarning(Option<LintExpectationId>),
ForceWarning,
/// A warning about the code being compiled. Does not prevent compilation from finishing.
/// Will be skipped if `can_emit_warnings` is false.
@ -1926,8 +1926,8 @@ pub enum Level {
/// Only used for lints.
Allow,
/// Only used for lints.
Expect(LintExpectationId),
/// Only used for lints. Requires a [`LintExpectationId`] for silencing the lints.
Expect,
}
impl fmt::Display for Level {
@ -1943,7 +1943,7 @@ impl Level {
Bug | Fatal | Error | DelayedBug => {
spec.set_fg(Some(Color::Red)).set_intense(true);
}
ForceWarning(_) | Warning => {
ForceWarning | Warning => {
spec.set_fg(Some(Color::Yellow)).set_intense(cfg!(windows));
}
Note | OnceNote => {
@ -1953,7 +1953,7 @@ impl Level {
spec.set_fg(Some(Color::Cyan)).set_intense(true);
}
FailureNote => {}
Allow | Expect(_) => unreachable!(),
Allow | Expect => unreachable!(),
}
spec
}
@ -1962,11 +1962,11 @@ impl Level {
match self {
Bug | DelayedBug => "error: internal compiler error",
Fatal | Error => "error",
ForceWarning(_) | Warning => "warning",
ForceWarning | Warning => "warning",
Note | OnceNote => "note",
Help | OnceHelp => "help",
FailureNote => "failure-note",
Allow | Expect(_) => unreachable!(),
Allow | Expect => unreachable!(),
}
}
@ -1977,8 +1977,7 @@ impl Level {
// Can this level be used in a subdiagnostic message?
fn can_be_subdiag(&self) -> bool {
match self {
Bug | DelayedBug | Fatal | Error | ForceWarning(_) | FailureNote | Allow
| Expect(_) => false,
Bug | DelayedBug | Fatal | Error | ForceWarning | FailureNote | Allow | Expect => false,
Warning | Note | Help | OnceNote | OnceHelp => true,
}

View file

@ -629,6 +629,8 @@ declare_features! (
(unstable, strict_provenance_lints, "1.61.0", Some(130351)),
/// Allows string patterns to dereference values to match them.
(unstable, string_deref_patterns, "1.67.0", Some(87121)),
/// Allows `super let` statements.
(incomplete, super_let, "CURRENT_RUSTC_VERSION", Some(139076)),
/// Allows subtrait items to shadow supertrait items.
(unstable, supertrait_item_shadowing, "1.86.0", Some(89151)),
/// Allows using `#[thread_local]` on `static` items.

View file

@ -10,9 +10,9 @@ use rustc_ast::{
LitKind, TraitObjectSyntax, UintTy, UnsafeBinderCastKind,
};
pub use rustc_ast::{
AttrId, AttrStyle, BinOp, BinOpKind, BindingMode, BorrowKind, BoundConstness, BoundPolarity,
ByRef, CaptureBy, DelimArgs, ImplPolarity, IsAuto, MetaItemInner, MetaItemLit, Movability,
Mutability, UnOp,
AssignOp, AssignOpKind, AttrId, AttrStyle, BinOp, BinOpKind, BindingMode, BorrowKind,
BoundConstness, BoundPolarity, ByRef, CaptureBy, DelimArgs, ImplPolarity, IsAuto,
MetaItemInner, MetaItemLit, Movability, Mutability, UnOp,
};
use rustc_attr_data_structures::AttributeKind;
use rustc_data_structures::fingerprint::Fingerprint;
@ -2648,7 +2648,7 @@ pub enum ExprKind<'hir> {
/// An assignment with an operator.
///
/// E.g., `a += 1`.
AssignOp(BinOp, &'hir Expr<'hir>, &'hir Expr<'hir>),
AssignOp(AssignOp, &'hir Expr<'hir>, &'hir Expr<'hir>),
/// Access of a named (e.g., `obj.foo`) or unnamed (e.g., `obj.0`) struct or tuple field.
Field(&'hir Expr<'hir>, Ident),
/// An indexing operation (`foo[2]`).

View file

@ -83,6 +83,12 @@ pub struct HirId {
pub local_id: ItemLocalId,
}
// To ensure correctness of incremental compilation,
// `HirId` must not implement `Ord` or `PartialOrd`.
// See https://github.com/rust-lang/rust/issues/90317.
impl !Ord for HirId {}
impl !PartialOrd for HirId {}
impl Debug for HirId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Example: HirId(DefId(0:1 ~ aa[7697]::{use#0}).10)
@ -116,10 +122,6 @@ impl HirId {
pub fn make_owner(owner: LocalDefId) -> Self {
Self { owner: OwnerId { def_id: owner }, local_id: ItemLocalId::ZERO }
}
pub fn index(self) -> (usize, usize) {
(rustc_index::Idx::index(self.owner.def_id), rustc_index::Idx::index(self.local_id))
}
}
impl fmt::Display for HirId {
@ -128,18 +130,6 @@ impl fmt::Display for HirId {
}
}
impl Ord for HirId {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
(self.index()).cmp(&(other.index()))
}
}
impl PartialOrd for HirId {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
rustc_data_structures::define_stable_id_collections!(HirIdMap, HirIdSet, HirIdMapEntry, HirId);
rustc_data_structures::define_id_collections!(
ItemLocalMap,

View file

@ -433,6 +433,12 @@ language_item_table! {
// Experimental lang items for implementing contract pre- and post-condition checking.
ContractBuildCheckEnsures, sym::contract_build_check_ensures, contract_build_check_ensures_fn, Target::Fn, GenericRequirement::None;
ContractCheckRequires, sym::contract_check_requires, contract_check_requires_fn, Target::Fn, GenericRequirement::None;
// Experimental lang items for `MCP: Low level components for async drop`(https://github.com/rust-lang/compiler-team/issues/727)
DefaultTrait4, sym::default_trait4, default_trait4_trait, Target::Trait, GenericRequirement::None;
DefaultTrait3, sym::default_trait3, default_trait3_trait, Target::Trait, GenericRequirement::None;
DefaultTrait2, sym::default_trait2, default_trait2_trait, Target::Trait, GenericRequirement::None;
DefaultTrait1, sym::default_trait1, default_trait1_trait, Target::Trait, GenericRequirement::None;
}
/// The requirement imposed on the generics of a lang item

View file

@ -11,6 +11,7 @@
#![feature(debug_closure_helpers)]
#![feature(exhaustive_patterns)]
#![feature(let_chains)]
#![feature(negative_impls)]
#![feature(never_type)]
#![feature(rustc_attrs)]
#![feature(variant_count)]

View file

@ -12,7 +12,6 @@ use rustc_hir::{self as hir, AmbigArg, GenericParamKind, ImplItemKind, intravisi
use rustc_infer::infer::{self, InferCtxt, TyCtxtInferExt};
use rustc_infer::traits::util;
use rustc_middle::ty::error::{ExpectedFound, TypeError};
use rustc_middle::ty::util::ExplicitSelf;
use rustc_middle::ty::{
self, BottomUpFolder, GenericArgs, GenericParamDefKind, Ty, TyCtxt, TypeFoldable, TypeFolder,
TypeSuperFoldable, TypeVisitableExt, TypingMode, Upcast,
@ -995,6 +994,26 @@ impl<'tcx> ty::FallibleTypeFolder<TyCtxt<'tcx>> for RemapHiddenTyRegions<'tcx> {
}
}
/// Gets the string for an explicit self declaration, e.g. "self", "&self",
/// etc.
fn get_self_string<'tcx, P>(self_arg_ty: Ty<'tcx>, is_self_ty: P) -> String
where
P: Fn(Ty<'tcx>) -> bool,
{
if is_self_ty(self_arg_ty) {
"self".to_owned()
} else if let ty::Ref(_, ty, mutbl) = self_arg_ty.kind()
&& is_self_ty(*ty)
{
match mutbl {
hir::Mutability::Not => "&self".to_owned(),
hir::Mutability::Mut => "&mut self".to_owned(),
}
} else {
format!("self: {self_arg_ty}")
}
}
fn report_trait_method_mismatch<'tcx>(
infcx: &InferCtxt<'tcx>,
mut cause: ObligationCause<'tcx>,
@ -1020,12 +1039,7 @@ fn report_trait_method_mismatch<'tcx>(
if trait_m.fn_has_self_parameter =>
{
let ty = trait_sig.inputs()[0];
let sugg = match ExplicitSelf::determine(ty, |ty| ty == impl_trait_ref.self_ty()) {
ExplicitSelf::ByValue => "self".to_owned(),
ExplicitSelf::ByReference(_, hir::Mutability::Not) => "&self".to_owned(),
ExplicitSelf::ByReference(_, hir::Mutability::Mut) => "&mut self".to_owned(),
_ => format!("self: {ty}"),
};
let sugg = get_self_string(ty, |ty| ty == impl_trait_ref.self_ty());
// When the `impl` receiver is an arbitrary self type, like `self: Box<Self>`, the
// span points only at the type `Box<Self`>, but we want to cover the whole
@ -1238,12 +1252,7 @@ fn compare_self_type<'tcx>(
.build_with_typing_env(ty::TypingEnv::non_body_analysis(tcx, method.def_id));
let self_arg_ty = tcx.liberate_late_bound_regions(method.def_id, self_arg_ty);
let can_eq_self = |ty| infcx.can_eq(param_env, untransformed_self_ty, ty);
match ExplicitSelf::determine(self_arg_ty, can_eq_self) {
ExplicitSelf::ByValue => "self".to_owned(),
ExplicitSelf::ByReference(_, hir::Mutability::Not) => "&self".to_owned(),
ExplicitSelf::ByReference(_, hir::Mutability::Mut) => "&mut self".to_owned(),
_ => format!("self: {self_arg_ty}"),
}
get_self_string(self_arg_ty, can_eq_self)
};
match (trait_m.fn_has_self_parameter, impl_m.fn_has_self_parameter) {

View file

@ -341,9 +341,8 @@ fn bounds_from_generic_predicates<'tcx>(
ty::ClauseKind::Trait(trait_predicate) => {
let entry = types.entry(trait_predicate.self_ty()).or_default();
let def_id = trait_predicate.def_id();
if Some(def_id) != tcx.lang_items().sized_trait() {
// Type params are `Sized` by default, do not add that restriction to the list
// if it is a positive requirement.
if !tcx.is_default_trait(def_id) {
// Do not add that restriction to the list if it is a positive requirement.
entry.push(trait_predicate.def_id());
}
}

View file

@ -61,6 +61,7 @@ pub(crate) fn provide(providers: &mut Providers) {
*providers = Providers {
type_of: type_of::type_of,
type_of_opaque: type_of::type_of_opaque,
type_of_opaque_hir_typeck: type_of::type_of_opaque_hir_typeck,
type_alias_is_lazy: type_of::type_alias_is_lazy,
item_bounds: item_bounds::item_bounds,
explicit_item_bounds: item_bounds::explicit_item_bounds,

View file

@ -38,13 +38,13 @@ fn associated_type_bounds<'tcx>(
let icx = ItemCtxt::new(tcx, assoc_item_def_id);
let mut bounds = Vec::new();
icx.lowerer().lower_bounds(item_ty, hir_bounds, &mut bounds, ty::List::empty(), filter);
// Associated types are implicitly sized unless a `?Sized` bound is found
// Implicit bounds are added to associated types unless a `?Trait` bound is found
match filter {
PredicateFilter::All
| PredicateFilter::SelfOnly
| PredicateFilter::SelfTraitThatDefines(_)
| PredicateFilter::SelfAndAssociatedTypeBounds => {
icx.lowerer().add_sized_bound(&mut bounds, item_ty, hir_bounds, None, span);
icx.lowerer().add_default_traits(&mut bounds, item_ty, hir_bounds, None, span);
}
// `ConstIfConst` is only interested in `~const` bounds.
PredicateFilter::ConstIfConst | PredicateFilter::SelfConstIfConst => {}
@ -327,14 +327,13 @@ fn opaque_type_bounds<'tcx>(
let icx = ItemCtxt::new(tcx, opaque_def_id);
let mut bounds = Vec::new();
icx.lowerer().lower_bounds(item_ty, hir_bounds, &mut bounds, ty::List::empty(), filter);
// Opaque types are implicitly sized unless a `?Sized` bound is found
// Implicit bounds are added to opaque types unless a `?Trait` bound is found
match filter {
PredicateFilter::All
| PredicateFilter::SelfOnly
| PredicateFilter::SelfTraitThatDefines(_)
| PredicateFilter::SelfAndAssociatedTypeBounds => {
// Associated types are implicitly sized unless a `?Sized` bound is found
icx.lowerer().add_sized_bound(&mut bounds, item_ty, hir_bounds, None, span);
icx.lowerer().add_default_traits(&mut bounds, item_ty, hir_bounds, None, span);
}
//`ConstIfConst` is only interested in `~const` bounds.
PredicateFilter::ConstIfConst | PredicateFilter::SelfConstIfConst => {}

View file

@ -165,12 +165,42 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen
ItemKind::Trait(_, _, _, _, self_bounds, ..)
| ItemKind::TraitAlias(_, _, self_bounds) => {
is_trait = Some(self_bounds);
is_trait = Some((self_bounds, item.span));
}
_ => {}
}
};
if let Node::TraitItem(item) = node {
let parent = tcx.local_parent(item.hir_id().owner.def_id);
let Node::Item(parent_trait) = tcx.hir_node_by_def_id(parent) else {
unreachable!();
};
let (trait_generics, trait_bounds) = match parent_trait.kind {
hir::ItemKind::Trait(_, _, _, generics, supertraits, _) => (generics, supertraits),
hir::ItemKind::TraitAlias(_, generics, supertraits) => (generics, supertraits),
_ => unreachable!(),
};
// Implicitly add `Self: DefaultAutoTrait` clauses on trait associated items if
// they are not added as super trait bounds to the trait itself. See comment on
// `requires_default_supertraits` for more details.
if !icx.lowerer().requires_default_supertraits(trait_bounds, trait_generics) {
let mut bounds = Vec::new();
let self_ty_where_predicates = (parent, item.generics.predicates);
icx.lowerer().add_default_traits_with_filter(
&mut bounds,
tcx.types.self_param,
&[],
Some(self_ty_where_predicates),
item.span,
|tr| tr != hir::LangItem::Sized,
);
predicates.extend(bounds);
}
}
let generics = tcx.generics_of(def_id);
// Below we'll consider the bounds on the type parameters (including `Self`)
@ -181,11 +211,18 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen
let mut bounds = Vec::new();
icx.lowerer().lower_bounds(
tcx.types.self_param,
self_bounds,
self_bounds.0,
&mut bounds,
ty::List::empty(),
PredicateFilter::All,
);
icx.lowerer().add_default_super_traits(
def_id,
&mut bounds,
self_bounds.0,
hir_generics,
self_bounds.1,
);
predicates.extend(bounds);
}
@ -210,8 +247,8 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen
GenericParamKind::Type { .. } => {
let param_ty = icx.lowerer().lower_ty_param(param.hir_id);
let mut bounds = Vec::new();
// Params are implicitly sized unless a `?Sized` bound is found
icx.lowerer().add_sized_bound(
// Implicit bounds are added to type params unless a `?Trait` bound is found
icx.lowerer().add_default_traits(
&mut bounds,
param_ty,
&[],
@ -625,6 +662,22 @@ pub(super) fn implied_predicates_with_filter<'tcx>(
let self_param_ty = tcx.types.self_param;
let mut bounds = Vec::new();
icx.lowerer().lower_bounds(self_param_ty, superbounds, &mut bounds, ty::List::empty(), filter);
match filter {
PredicateFilter::All
| PredicateFilter::SelfOnly
| PredicateFilter::SelfTraitThatDefines(_)
| PredicateFilter::SelfAndAssociatedTypeBounds => {
icx.lowerer().add_default_super_traits(
trait_def_id,
&mut bounds,
superbounds,
generics,
item.span,
);
}
//`ConstIfConst` is only interested in `~const` bounds.
PredicateFilter::ConstIfConst | PredicateFilter::SelfConstIfConst => {}
}
let where_bounds_that_match =
icx.probe_ty_param_bounds_in_generics(generics, item.owner_id.def_id, filter);

View file

@ -7,7 +7,9 @@ use rustc_hir::{self as hir, AmbigArg, HirId};
use rustc_middle::query::plumbing::CyclePlaceholder;
use rustc_middle::ty::print::with_forced_trimmed_paths;
use rustc_middle::ty::util::IntTypeExt;
use rustc_middle::ty::{self, IsSuggestable, Ty, TyCtxt, TypeVisitableExt, fold_regions};
use rustc_middle::ty::{
self, DefiningScopeKind, IsSuggestable, Ty, TyCtxt, TypeVisitableExt, fold_regions,
};
use rustc_middle::{bug, span_bug};
use rustc_span::{DUMMY_SP, Ident, Span};
@ -324,10 +326,18 @@ pub(super) fn type_of_opaque(
if let Some(def_id) = def_id.as_local() {
Ok(ty::EarlyBinder::bind(match tcx.hir_node_by_def_id(def_id).expect_opaque_ty().origin {
hir::OpaqueTyOrigin::TyAlias { in_assoc_ty: false, .. } => {
opaque::find_opaque_ty_constraints_for_tait(tcx, def_id)
opaque::find_opaque_ty_constraints_for_tait(
tcx,
def_id,
DefiningScopeKind::MirBorrowck,
)
}
hir::OpaqueTyOrigin::TyAlias { in_assoc_ty: true, .. } => {
opaque::find_opaque_ty_constraints_for_impl_trait_in_assoc_type(tcx, def_id)
opaque::find_opaque_ty_constraints_for_impl_trait_in_assoc_type(
tcx,
def_id,
DefiningScopeKind::MirBorrowck,
)
}
// Opaque types desugared from `impl Trait`.
hir::OpaqueTyOrigin::FnReturn { parent: owner, in_trait_or_impl }
@ -340,7 +350,12 @@ pub(super) fn type_of_opaque(
"tried to get type of this RPITIT with no definition"
);
}
opaque::find_opaque_ty_constraints_for_rpit(tcx, def_id, owner)
opaque::find_opaque_ty_constraints_for_rpit(
tcx,
def_id,
owner,
DefiningScopeKind::MirBorrowck,
)
}
}))
} else {
@ -350,6 +365,42 @@ pub(super) fn type_of_opaque(
}
}
pub(super) fn type_of_opaque_hir_typeck(
tcx: TyCtxt<'_>,
def_id: LocalDefId,
) -> ty::EarlyBinder<'_, Ty<'_>> {
ty::EarlyBinder::bind(match tcx.hir_node_by_def_id(def_id).expect_opaque_ty().origin {
hir::OpaqueTyOrigin::TyAlias { in_assoc_ty: false, .. } => {
opaque::find_opaque_ty_constraints_for_tait(tcx, def_id, DefiningScopeKind::HirTypeck)
}
hir::OpaqueTyOrigin::TyAlias { in_assoc_ty: true, .. } => {
opaque::find_opaque_ty_constraints_for_impl_trait_in_assoc_type(
tcx,
def_id,
DefiningScopeKind::HirTypeck,
)
}
// Opaque types desugared from `impl Trait`.
hir::OpaqueTyOrigin::FnReturn { parent: owner, in_trait_or_impl }
| hir::OpaqueTyOrigin::AsyncFn { parent: owner, in_trait_or_impl } => {
if in_trait_or_impl == Some(hir::RpitContext::Trait)
&& !tcx.defaultness(owner).has_value()
{
span_bug!(
tcx.def_span(def_id),
"tried to get type of this RPITIT with no definition"
);
}
opaque::find_opaque_ty_constraints_for_rpit(
tcx,
def_id,
owner,
DefiningScopeKind::HirTypeck,
)
}
})
}
fn infer_placeholder_type<'tcx>(
cx: &dyn HirTyLowerer<'tcx>,
def_id: LocalDefId,

View file

@ -3,8 +3,7 @@ use rustc_hir::def_id::LocalDefId;
use rustc_hir::{self as hir, Expr, ImplItem, Item, Node, TraitItem, def, intravisit};
use rustc_middle::bug;
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt};
use rustc_span::DUMMY_SP;
use rustc_middle::ty::{self, DefiningScopeKind, Ty, TyCtxt, TypeVisitableExt};
use tracing::{debug, instrument, trace};
use crate::errors::{TaitForwardCompat2, UnconstrainedOpaqueType};
@ -15,6 +14,7 @@ use crate::errors::{TaitForwardCompat2, UnconstrainedOpaqueType};
pub(super) fn find_opaque_ty_constraints_for_impl_trait_in_assoc_type(
tcx: TyCtxt<'_>,
def_id: LocalDefId,
opaque_types_from: DefiningScopeKind,
) -> Ty<'_> {
let mut parent_def_id = def_id;
while tcx.def_kind(parent_def_id) == def::DefKind::OpaqueTy {
@ -27,7 +27,7 @@ pub(super) fn find_opaque_ty_constraints_for_impl_trait_in_assoc_type(
other => bug!("invalid impl trait in assoc type parent: {other:?}"),
}
let mut locator = TaitConstraintLocator { def_id, tcx, found: None, typeck_types: vec![] };
let mut locator = TaitConstraintLocator { def_id, tcx, found: None, opaque_types_from };
for &assoc_id in tcx.associated_item_def_ids(impl_def_id) {
let assoc = tcx.associated_item(assoc_id);
@ -39,25 +39,14 @@ pub(super) fn find_opaque_ty_constraints_for_impl_trait_in_assoc_type(
}
if let Some(hidden) = locator.found {
// Only check against typeck if we didn't already error
if !hidden.ty.references_error() {
for concrete_type in locator.typeck_types {
if concrete_type.ty != tcx.erase_regions(hidden.ty) {
if let Ok(d) = hidden.build_mismatch_error(&concrete_type, tcx) {
d.emit();
}
}
}
}
hidden.ty
} else {
let reported = tcx.dcx().emit_err(UnconstrainedOpaqueType {
let guar = tcx.dcx().emit_err(UnconstrainedOpaqueType {
span: tcx.def_span(def_id),
name: tcx.item_ident(parent_def_id.to_def_id()),
what: "impl",
});
Ty::new_error(tcx, reported)
Ty::new_error(tcx, guar)
}
}
@ -80,23 +69,16 @@ pub(super) fn find_opaque_ty_constraints_for_impl_trait_in_assoc_type(
/// fn b<T>() -> Foo<T, u32> { .. }
/// ```
#[instrument(skip(tcx), level = "debug")]
pub(super) fn find_opaque_ty_constraints_for_tait(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Ty<'_> {
let mut locator = TaitConstraintLocator { def_id, tcx, found: None, typeck_types: vec![] };
pub(super) fn find_opaque_ty_constraints_for_tait(
tcx: TyCtxt<'_>,
def_id: LocalDefId,
opaque_types_from: DefiningScopeKind,
) -> Ty<'_> {
let mut locator = TaitConstraintLocator { def_id, tcx, found: None, opaque_types_from };
tcx.hir_walk_toplevel_module(&mut locator);
if let Some(hidden) = locator.found {
// Only check against typeck if we didn't already error
if !hidden.ty.references_error() {
for concrete_type in locator.typeck_types {
if concrete_type.ty != tcx.erase_regions(hidden.ty) {
if let Ok(d) = hidden.build_mismatch_error(&concrete_type, tcx) {
d.emit();
}
}
}
}
hidden.ty
} else {
let mut parent_def_id = def_id;
@ -104,12 +86,12 @@ pub(super) fn find_opaque_ty_constraints_for_tait(tcx: TyCtxt<'_>, def_id: Local
// Account for `type Alias = impl Trait<Foo = impl Trait>;` (#116031)
parent_def_id = tcx.local_parent(parent_def_id);
}
let reported = tcx.dcx().emit_err(UnconstrainedOpaqueType {
let guar = tcx.dcx().emit_err(UnconstrainedOpaqueType {
span: tcx.def_span(def_id),
name: tcx.item_ident(parent_def_id.to_def_id()),
what: "crate",
});
Ty::new_error(tcx, reported)
Ty::new_error(tcx, guar)
}
}
@ -126,22 +108,44 @@ struct TaitConstraintLocator<'tcx> {
/// type).
found: Option<ty::OpaqueHiddenType<'tcx>>,
/// In the presence of dead code, typeck may figure out a hidden type
/// while borrowck will not. We collect these cases here and check at
/// the end that we actually found a type that matches (modulo regions).
typeck_types: Vec<ty::OpaqueHiddenType<'tcx>>,
opaque_types_from: DefiningScopeKind,
}
impl TaitConstraintLocator<'_> {
impl<'tcx> TaitConstraintLocator<'tcx> {
fn insert_found(&mut self, hidden_ty: ty::OpaqueHiddenType<'tcx>) {
if let Some(prev) = &mut self.found {
if hidden_ty.ty != prev.ty {
let (Ok(guar) | Err(guar)) =
prev.build_mismatch_error(&hidden_ty, self.tcx).map(|d| d.emit());
prev.ty = Ty::new_error(self.tcx, guar);
}
} else {
self.found = Some(hidden_ty);
}
}
fn non_defining_use_in_defining_scope(&mut self, item_def_id: LocalDefId) {
let guar = self.tcx.dcx().emit_err(TaitForwardCompat2 {
span: self
.tcx
.def_ident_span(item_def_id)
.unwrap_or_else(|| self.tcx.def_span(item_def_id)),
opaque_type_span: self.tcx.def_span(self.def_id),
opaque_type: self.tcx.def_path_str(self.def_id),
});
self.insert_found(ty::OpaqueHiddenType::new_error(self.tcx, guar));
}
#[instrument(skip(self), level = "debug")]
fn check(&mut self, item_def_id: LocalDefId) {
// Don't try to check items that cannot possibly constrain the type.
if !self.tcx.has_typeck_results(item_def_id) {
let tcx = self.tcx;
if !tcx.has_typeck_results(item_def_id) {
debug!("no constraint: no typeck results");
return;
}
let opaque_types_defined_by = self.tcx.opaque_types_defined_by(item_def_id);
let opaque_types_defined_by = tcx.opaque_types_defined_by(item_def_id);
// Don't try to check items that cannot possibly constrain the type.
if !opaque_types_defined_by.contains(&self.def_id) {
debug!("no constraint: no opaque types defined");
@ -152,7 +156,7 @@ impl TaitConstraintLocator<'_> {
// "non-defining use" errors for them.
// Note that we use `Node::fn_sig` instead of `Node::fn_decl` here, because the former
// excludes closures, which are allowed to have `_` in their return type.
let hir_node = self.tcx.hir_node_by_def_id(item_def_id);
let hir_node = tcx.hir_node_by_def_id(item_def_id);
debug_assert!(
!matches!(hir_node, Node::ForeignItem(..)),
"foreign items cannot constrain opaque types",
@ -164,88 +168,39 @@ impl TaitConstraintLocator<'_> {
hir_sig.decl.output.span(),
"inferring return types and opaque types do not mix well",
);
self.found =
Some(ty::OpaqueHiddenType { span: DUMMY_SP, ty: Ty::new_error(self.tcx, guar) });
self.found = Some(ty::OpaqueHiddenType::new_error(tcx, guar));
return;
}
// Calling `mir_borrowck` can lead to cycle errors through
// const-checking, avoid calling it if we don't have to.
// ```rust
// type Foo = impl Fn() -> usize; // when computing type for this
// const fn bar() -> Foo {
// || 0usize
// }
// const BAZR: Foo = bar(); // we would mir-borrowck this, causing cycles
// // because we again need to reveal `Foo` so we can check whether the
// // constant does not contain interior mutability.
// ```
let tables = self.tcx.typeck(item_def_id);
if let Some(guar) = tables.tainted_by_errors {
self.found =
Some(ty::OpaqueHiddenType { span: DUMMY_SP, ty: Ty::new_error(self.tcx, guar) });
return;
}
let mut constrained = false;
for (&opaque_type_key, &hidden_type) in &tables.concrete_opaque_types {
if opaque_type_key.def_id != self.def_id {
continue;
}
constrained = true;
let concrete_type =
self.tcx.erase_regions(hidden_type.remap_generic_params_to_declaration_params(
opaque_type_key,
self.tcx,
true,
));
if self.typeck_types.iter().all(|prev| prev.ty != concrete_type.ty) {
self.typeck_types.push(concrete_type);
}
}
if !constrained {
debug!("no constraints in typeck results");
if opaque_types_defined_by.contains(&self.def_id) {
let guar = self.tcx.dcx().emit_err(TaitForwardCompat2 {
span: self
.tcx
.def_ident_span(item_def_id)
.unwrap_or_else(|| self.tcx.def_span(item_def_id)),
opaque_type_span: self.tcx.def_span(self.def_id),
opaque_type: self.tcx.def_path_str(self.def_id),
});
// Avoid "opaque type not constrained" errors on the opaque itself.
self.found = Some(ty::OpaqueHiddenType {
span: DUMMY_SP,
ty: Ty::new_error(self.tcx, guar),
});
}
return;
};
// Use borrowck to get the type with unerased regions.
let borrowck_results = &self.tcx.mir_borrowck(item_def_id);
// If the body was tainted, then assume the opaque may have been constrained and just set it to error.
if let Some(guar) = borrowck_results.tainted_by_errors {
self.found =
Some(ty::OpaqueHiddenType { span: DUMMY_SP, ty: Ty::new_error(self.tcx, guar) });
return;
}
debug!(?borrowck_results.concrete_opaque_types);
if let Some(&concrete_type) = borrowck_results.concrete_opaque_types.get(&self.def_id) {
debug!(?concrete_type, "found constraint");
if let Some(prev) = &mut self.found {
if concrete_type.ty != prev.ty {
let (Ok(guar) | Err(guar)) =
prev.build_mismatch_error(&concrete_type, self.tcx).map(|d| d.emit());
prev.ty = Ty::new_error(self.tcx, guar);
match self.opaque_types_from {
DefiningScopeKind::HirTypeck => {
let tables = tcx.typeck(item_def_id);
if let Some(guar) = tables.tainted_by_errors {
self.insert_found(ty::OpaqueHiddenType::new_error(tcx, guar));
} else if let Some(&hidden_type) = tables.concrete_opaque_types.get(&self.def_id) {
self.insert_found(hidden_type);
} else {
self.non_defining_use_in_defining_scope(item_def_id);
}
}
DefiningScopeKind::MirBorrowck => {
let borrowck_result = tcx.mir_borrowck(item_def_id);
if let Some(guar) = borrowck_result.tainted_by_errors {
self.insert_found(ty::OpaqueHiddenType::new_error(tcx, guar));
} else if let Some(&hidden_type) =
borrowck_result.concrete_opaque_types.get(&self.def_id)
{
debug!(?hidden_type, "found constraint");
self.insert_found(hidden_type);
} else if let Err(guar) = tcx
.type_of_opaque_hir_typeck(self.def_id)
.instantiate_identity()
.error_reported()
{
self.insert_found(ty::OpaqueHiddenType::new_error(tcx, guar));
} else {
self.non_defining_use_in_defining_scope(item_def_id);
}
} else {
self.found = Some(concrete_type);
}
}
}
@ -287,126 +242,42 @@ pub(super) fn find_opaque_ty_constraints_for_rpit<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: LocalDefId,
owner_def_id: LocalDefId,
opaque_types_from: DefiningScopeKind,
) -> Ty<'tcx> {
let tables = tcx.typeck(owner_def_id);
// Check that all of the opaques we inferred during HIR are compatible.
// FIXME: We explicitly don't check that the types inferred during HIR
// typeck are compatible with the one that we infer during borrowck,
// because that one actually sometimes has consts evaluated eagerly so
// using strict type equality will fail.
let mut hir_opaque_ty: Option<ty::OpaqueHiddenType<'tcx>> = None;
if tables.tainted_by_errors.is_none() {
for (&opaque_type_key, &hidden_type) in &tables.concrete_opaque_types {
if opaque_type_key.def_id != def_id {
continue;
}
let concrete_type = tcx.erase_regions(
hidden_type.remap_generic_params_to_declaration_params(opaque_type_key, tcx, true),
);
if let Some(prev) = &mut hir_opaque_ty {
if concrete_type.ty != prev.ty {
if let Ok(d) = prev.build_mismatch_error(&concrete_type, tcx) {
d.emit();
}
}
match opaque_types_from {
DefiningScopeKind::HirTypeck => {
let tables = tcx.typeck(owner_def_id);
if let Some(guar) = tables.tainted_by_errors {
Ty::new_error(tcx, guar)
} else if let Some(hidden_ty) = tables.concrete_opaque_types.get(&def_id) {
hidden_ty.ty
} else {
hir_opaque_ty = Some(concrete_type);
// FIXME(-Znext-solver): This should not be necessary and we should
// instead rely on inference variable fallback inside of typeck itself.
// We failed to resolve the opaque type or it
// resolves to itself. We interpret this as the
// no values of the hidden type ever being constructed,
// so we can just make the hidden type be `!`.
// For backwards compatibility reasons, we fall back to
// `()` until we the diverging default is changed.
Ty::new_diverging_default(tcx)
}
}
}
let mir_opaque_ty = tcx.mir_borrowck(owner_def_id).concrete_opaque_types.get(&def_id).copied();
if let Some(mir_opaque_ty) = mir_opaque_ty {
if mir_opaque_ty.references_error() {
return mir_opaque_ty.ty;
}
debug!(?owner_def_id);
let mut locator = RpitConstraintChecker { def_id, tcx, found: mir_opaque_ty };
match tcx.hir_node_by_def_id(owner_def_id) {
Node::Item(it) => intravisit::walk_item(&mut locator, it),
Node::ImplItem(it) => intravisit::walk_impl_item(&mut locator, it),
Node::TraitItem(it) => intravisit::walk_trait_item(&mut locator, it),
other => bug!("{:?} is not a valid scope for an opaque type item", other),
}
mir_opaque_ty.ty
} else if let Some(guar) = tables.tainted_by_errors {
// Some error in the owner fn prevented us from populating
// the `concrete_opaque_types` table.
Ty::new_error(tcx, guar)
} else {
// Fall back to the RPIT we inferred during HIR typeck
if let Some(hir_opaque_ty) = hir_opaque_ty {
hir_opaque_ty.ty
} else {
// We failed to resolve the opaque type or it
// resolves to itself. We interpret this as the
// no values of the hidden type ever being constructed,
// so we can just make the hidden type be `!`.
// For backwards compatibility reasons, we fall back to
// `()` until we the diverging default is changed.
Ty::new_diverging_default(tcx)
}
}
}
struct RpitConstraintChecker<'tcx> {
tcx: TyCtxt<'tcx>,
/// def_id of the opaque type whose defining uses are being checked
def_id: LocalDefId,
found: ty::OpaqueHiddenType<'tcx>,
}
impl RpitConstraintChecker<'_> {
#[instrument(skip(self), level = "debug")]
fn check(&self, def_id: LocalDefId) {
// Use borrowck to get the type with unerased regions.
let concrete_opaque_types = &self.tcx.mir_borrowck(def_id).concrete_opaque_types;
debug!(?concrete_opaque_types);
if let Some(&concrete_type) = concrete_opaque_types.get(&self.def_id) {
debug!(?concrete_type, "found constraint");
if concrete_type.ty != self.found.ty {
if let Ok(d) = self.found.build_mismatch_error(&concrete_type, self.tcx) {
d.emit();
DefiningScopeKind::MirBorrowck => {
let borrowck_result = tcx.mir_borrowck(owner_def_id);
if let Some(guar) = borrowck_result.tainted_by_errors {
Ty::new_error(tcx, guar)
} else if let Some(hidden_ty) = borrowck_result.concrete_opaque_types.get(&def_id) {
hidden_ty.ty
} else {
let hir_ty = tcx.type_of_opaque_hir_typeck(def_id).instantiate_identity();
if let Err(guar) = hir_ty.error_reported() {
Ty::new_error(tcx, guar)
} else {
hir_ty
}
}
}
}
}
impl<'tcx> intravisit::Visitor<'tcx> for RpitConstraintChecker<'tcx> {
type NestedFilter = nested_filter::OnlyBodies;
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
self.tcx
}
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
intravisit::walk_expr(self, ex);
}
fn visit_item(&mut self, it: &'tcx Item<'tcx>) {
trace!(?it.owner_id);
// The opaque type itself or its children are not within its reveal scope.
if it.owner_id.def_id != self.def_id {
self.check(it.owner_id.def_id);
intravisit::walk_item(self, it);
}
}
fn visit_impl_item(&mut self, it: &'tcx ImplItem<'tcx>) {
trace!(?it.owner_id);
// The opaque type itself or its children are not within its reveal scope.
if it.owner_id.def_id != self.def_id {
self.check(it.owner_id.def_id);
intravisit::walk_impl_item(self, it);
}
}
fn visit_trait_item(&mut self, it: &'tcx TraitItem<'tcx>) {
trace!(?it.owner_id);
self.check(it.owner_id.def_id);
intravisit::walk_trait_item(self, it);
}
}

View file

@ -4,9 +4,9 @@ use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
use rustc_errors::codes::*;
use rustc_errors::struct_span_code_err;
use rustc_hir as hir;
use rustc_hir::HirId;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::{AmbigArg, HirId};
use rustc_middle::bug;
use rustc_middle::ty::{
self as ty, IsSuggestable, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt,
@ -24,25 +24,190 @@ use crate::hir_ty_lowering::{
};
impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
/// Add a `Sized` bound to the `bounds` if appropriate.
///
/// Doesn't add the bound if the HIR bounds contain any of `Sized`, `?Sized` or `!Sized`.
pub(crate) fn add_sized_bound(
pub(crate) fn add_default_traits(
&self,
bounds: &mut Vec<(ty::Clause<'tcx>, Span)>,
self_ty: Ty<'tcx>,
hir_bounds: &'tcx [hir::GenericBound<'tcx>],
hir_bounds: &[hir::GenericBound<'tcx>],
self_ty_where_predicates: Option<(LocalDefId, &'tcx [hir::WherePredicate<'tcx>])>,
span: Span,
) {
self.add_default_traits_with_filter(
bounds,
self_ty,
hir_bounds,
self_ty_where_predicates,
span,
|_| true,
);
}
/// Checks whether `Self: DefaultAutoTrait` bounds should be added on trait super bounds
/// or associative items.
///
/// To keep backward compatibility with existing code, `experimental_default_bounds` bounds
/// should be added everywhere, including super bounds. However this causes a huge performance
/// costs. For optimization purposes instead of adding default supertraits, bounds
/// are added to the associative items:
///
/// ```ignore(illustrative)
/// // Default bounds are generated in the following way:
/// trait Trait {
/// fn foo(&self) where Self: Leak {}
/// }
///
/// // instead of this:
/// trait Trait: Leak {
/// fn foo(&self) {}
/// }
/// ```
/// It is not always possible to do this because of backward compatibility:
///
/// ```ignore(illustrative)
/// pub trait Trait<Rhs = Self> {}
/// pub trait Trait1 : Trait {}
/// //~^ ERROR: `Rhs` requires `DefaultAutoTrait`, but `Self` is not `DefaultAutoTrait`
/// ```
///
/// or:
///
/// ```ignore(illustrative)
/// trait Trait {
/// type Type where Self: Sized;
/// }
/// trait Trait2<T> : Trait<Type = T> {}
/// //~^ ERROR: `DefaultAutoTrait` required for `Trait2`, by implicit `Self: DefaultAutoTrait` in `Trait::Type`
/// ```
///
/// Therefore, `experimental_default_bounds` are still being added to supertraits if
/// the `SelfTyParam` or `AssocItemConstraint` were found in a trait header.
pub(crate) fn requires_default_supertraits(
&self,
hir_bounds: &'tcx [hir::GenericBound<'tcx>],
hir_generics: &'tcx hir::Generics<'tcx>,
) -> bool {
struct TraitInfoCollector;
impl<'tcx> hir::intravisit::Visitor<'tcx> for TraitInfoCollector {
type Result = ControlFlow<()>;
fn visit_assoc_item_constraint(
&mut self,
_constraint: &'tcx hir::AssocItemConstraint<'tcx>,
) -> Self::Result {
ControlFlow::Break(())
}
fn visit_ty(&mut self, t: &'tcx hir::Ty<'tcx, AmbigArg>) -> Self::Result {
if matches!(
&t.kind,
hir::TyKind::Path(hir::QPath::Resolved(
_,
hir::Path { res: hir::def::Res::SelfTyParam { .. }, .. },
))
) {
return ControlFlow::Break(());
}
hir::intravisit::walk_ty(self, t)
}
}
let mut found = false;
for bound in hir_bounds {
found |= hir::intravisit::walk_param_bound(&mut TraitInfoCollector, bound).is_break();
}
found |= hir::intravisit::walk_generics(&mut TraitInfoCollector, hir_generics).is_break();
found
}
/// Lazily sets `experimental_default_bounds` to true on trait super bounds.
/// See `requires_default_supertraits` for more information.
pub(crate) fn add_default_super_traits(
&self,
trait_def_id: LocalDefId,
bounds: &mut Vec<(ty::Clause<'tcx>, Span)>,
hir_bounds: &'tcx [hir::GenericBound<'tcx>],
hir_generics: &'tcx hir::Generics<'tcx>,
span: Span,
) {
assert!(matches!(self.tcx().def_kind(trait_def_id), DefKind::Trait | DefKind::TraitAlias));
if self.requires_default_supertraits(hir_bounds, hir_generics) {
let self_ty_where_predicates = (trait_def_id, hir_generics.predicates);
self.add_default_traits_with_filter(
bounds,
self.tcx().types.self_param,
hir_bounds,
Some(self_ty_where_predicates),
span,
|default_trait| default_trait != hir::LangItem::Sized,
);
}
}
pub(crate) fn add_default_traits_with_filter(
&self,
bounds: &mut Vec<(ty::Clause<'tcx>, Span)>,
self_ty: Ty<'tcx>,
hir_bounds: &[hir::GenericBound<'tcx>],
self_ty_where_predicates: Option<(LocalDefId, &'tcx [hir::WherePredicate<'tcx>])>,
span: Span,
f: impl Fn(hir::LangItem) -> bool,
) {
self.tcx().default_traits().iter().filter(|&&default_trait| f(default_trait)).for_each(
|default_trait| {
self.add_default_trait(
*default_trait,
bounds,
self_ty,
hir_bounds,
self_ty_where_predicates,
span,
);
},
);
}
/// Add a `Sized` or `experimental_default_bounds` bounds to the `bounds` if appropriate.
///
/// Doesn't add the bound if the HIR bounds contain any of `Trait`, `?Trait` or `!Trait`.
pub(crate) fn add_default_trait(
&self,
trait_: hir::LangItem,
bounds: &mut Vec<(ty::Clause<'tcx>, Span)>,
self_ty: Ty<'tcx>,
hir_bounds: &[hir::GenericBound<'tcx>],
self_ty_where_predicates: Option<(LocalDefId, &'tcx [hir::WherePredicate<'tcx>])>,
span: Span,
) {
let trait_id = self.tcx().lang_items().get(trait_);
if let Some(trait_id) = trait_id
&& self.do_not_provide_default_trait_bound(
trait_id,
hir_bounds,
self_ty_where_predicates,
)
{
// There was no `?Trait` or `!Trait` bound;
// add `Trait` if it's available.
let trait_ref = ty::TraitRef::new(self.tcx(), trait_id, [self_ty]);
// Preferable to put this obligation first, since we report better errors for sized ambiguity.
bounds.insert(0, (trait_ref.upcast(self.tcx()), span));
}
}
fn do_not_provide_default_trait_bound<'a>(
&self,
trait_def_id: DefId,
hir_bounds: &'a [hir::GenericBound<'tcx>],
self_ty_where_predicates: Option<(LocalDefId, &'tcx [hir::WherePredicate<'tcx>])>,
) -> bool {
let tcx = self.tcx();
let sized_def_id = tcx.lang_items().sized_trait();
let mut seen_negative_sized_bound = false;
let mut seen_positive_sized_bound = false;
let mut seen_negative_bound = false;
let mut seen_positive_bound = false;
// Try to find an unbound in bounds.
let mut unbounds: SmallVec<[_; 1]> = SmallVec::new();
let mut search_bounds = |hir_bounds: &'tcx [hir::GenericBound<'tcx>]| {
let mut search_bounds = |hir_bounds: &'a [hir::GenericBound<'tcx>]| {
for hir_bound in hir_bounds {
let hir::GenericBound::Trait(ptr) = hir_bound else {
continue;
@ -50,17 +215,13 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
match ptr.modifiers.polarity {
hir::BoundPolarity::Maybe(_) => unbounds.push(ptr),
hir::BoundPolarity::Negative(_) => {
if let Some(sized_def_id) = sized_def_id
&& ptr.trait_ref.path.res == Res::Def(DefKind::Trait, sized_def_id)
{
seen_negative_sized_bound = true;
if ptr.trait_ref.path.res == Res::Def(DefKind::Trait, trait_def_id) {
seen_negative_bound = true;
}
}
hir::BoundPolarity::Positive => {
if let Some(sized_def_id) = sized_def_id
&& ptr.trait_ref.path.res == Res::Def(DefKind::Trait, sized_def_id)
{
seen_positive_sized_bound = true;
if ptr.trait_ref.path.res == Res::Def(DefKind::Trait, trait_def_id) {
seen_positive_bound = true;
}
}
}
@ -95,32 +256,36 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
};
}
let mut seen_sized_unbound = false;
let mut seen_unbound = false;
for unbound in unbounds {
if let Some(sized_def_id) = sized_def_id
&& unbound.trait_ref.path.res == Res::Def(DefKind::Trait, sized_def_id)
{
seen_sized_unbound = true;
continue;
let unbound_def_id = unbound.trait_ref.trait_def_id();
if unbound_def_id == Some(trait_def_id) {
seen_unbound = true;
}
let emit_relax_err = || {
let unbound_traits =
match self.tcx().sess.opts.unstable_opts.experimental_default_bounds {
true => "`?Sized` and `experimental_default_bounds`",
false => "`?Sized`",
};
// There was a `?Trait` bound, but it was neither `?Sized` nor `experimental_default_bounds`.
tcx.dcx().span_err(
unbound.span,
format!(
"relaxing a default bound only does something for {}; \
all other traits are not bound by default",
unbound_traits
),
);
};
match unbound_def_id {
Some(def_id) if !tcx.is_default_trait(def_id) => emit_relax_err(),
None => emit_relax_err(),
_ => {}
}
// There was a `?Trait` bound, but it was not `?Sized`
self.dcx().span_err(
unbound.span,
"relaxing a default bound only does something for `?Sized`; \
all other traits are not bound by default",
);
}
if seen_sized_unbound || seen_negative_sized_bound || seen_positive_sized_bound {
// There was in fact a `?Sized`, `!Sized` or explicit `Sized` bound;
// we don't need to do anything.
} else if let Some(sized_def_id) = sized_def_id {
// There was no `?Sized`, `!Sized` or explicit `Sized` bound;
// add `Sized` if it's available.
let trait_ref = ty::TraitRef::new(tcx, sized_def_id, [self_ty]);
// Preferable to put this obligation first, since we report better errors for sized ambiguity.
bounds.insert(0, (trait_ref.upcast(tcx), span));
}
!(seen_unbound || seen_negative_bound || seen_positive_bound)
}
/// Lower HIR bounds into `bounds` given the self type `param_ty` and the overarching late-bound vars if any.

View file

@ -57,6 +57,18 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
}
}
let ast_bounds: Vec<_> =
hir_bounds.iter().map(|&trait_ref| hir::GenericBound::Trait(trait_ref)).collect();
self.add_default_traits_with_filter(
&mut user_written_bounds,
dummy_self,
&ast_bounds,
None,
span,
|tr| tr != hir::LangItem::Sized,
);
let (elaborated_trait_bounds, elaborated_projection_bounds) =
traits::expand_trait_aliases(tcx, user_written_bounds.iter().copied());
let (regular_traits, mut auto_traits): (Vec<_>, Vec<_>) = elaborated_trait_bounds

View file

@ -838,7 +838,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
| PredicateFilter::SelfOnly
| PredicateFilter::SelfAndAssociatedTypeBounds => {
match constness {
hir::BoundConstness::Always(span) => {
hir::BoundConstness::Always(_) => {
if polarity == ty::PredicatePolarity::Positive {
bounds.push((
poly_trait_ref
@ -864,7 +864,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
// in `lower_assoc_item_constraint`.
PredicateFilter::ConstIfConst | PredicateFilter::SelfConstIfConst => {
match constness {
hir::BoundConstness::Maybe(span) => {
hir::BoundConstness::Maybe(_) => {
if polarity == ty::PredicatePolarity::Positive {
bounds.push((
poly_trait_ref

View file

@ -1271,18 +1271,18 @@ impl<'a> State<'a> {
self.print_call_post(base_args)
}
fn print_expr_binary(&mut self, op: hir::BinOp, lhs: &hir::Expr<'_>, rhs: &hir::Expr<'_>) {
let binop_prec = op.node.precedence();
fn print_expr_binary(&mut self, op: hir::BinOpKind, lhs: &hir::Expr<'_>, rhs: &hir::Expr<'_>) {
let binop_prec = op.precedence();
let left_prec = lhs.precedence();
let right_prec = rhs.precedence();
let (mut left_needs_paren, right_needs_paren) = match op.node.fixity() {
let (mut left_needs_paren, right_needs_paren) = match 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),
};
match (&lhs.kind, op.node) {
match (&lhs.kind, op) {
// 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.
@ -1297,7 +1297,7 @@ impl<'a> State<'a> {
self.print_expr_cond_paren(lhs, left_needs_paren);
self.space();
self.word_space(op.node.as_str());
self.word_space(op.as_str());
self.print_expr_cond_paren(rhs, right_needs_paren);
}
@ -1451,7 +1451,7 @@ impl<'a> State<'a> {
self.word(".use");
}
hir::ExprKind::Binary(op, lhs, rhs) => {
self.print_expr_binary(op, lhs, rhs);
self.print_expr_binary(op.node, lhs, rhs);
}
hir::ExprKind::Unary(op, expr) => {
self.print_expr_unary(op, expr);
@ -1572,8 +1572,7 @@ impl<'a> State<'a> {
hir::ExprKind::AssignOp(op, lhs, rhs) => {
self.print_expr_cond_paren(lhs, lhs.precedence() <= ExprPrecedence::Assign);
self.space();
self.word(op.node.as_str());
self.word_space("=");
self.word_space(op.node.as_str());
self.print_expr_cond_paren(rhs, rhs.precedence() < ExprPrecedence::Assign);
}
hir::ExprKind::Field(expr, ident) => {

View file

@ -512,7 +512,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.check_expr_assign(expr, expected, lhs, rhs, span)
}
ExprKind::AssignOp(op, lhs, rhs) => {
self.check_expr_binop_assign(expr, op, lhs, rhs, expected)
self.check_expr_assign_op(expr, op, lhs, rhs, expected)
}
ExprKind::Unary(unop, oprnd) => self.check_expr_unop(unop, oprnd, expected, expr),
ExprKind::AddrOf(kind, mutbl, oprnd) => {

View file

@ -3477,30 +3477,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
lhs_ty: Ty<'tcx>,
rhs_expr: &'tcx hir::Expr<'tcx>,
lhs_expr: &'tcx hir::Expr<'tcx>,
op: hir::BinOp,
) {
match op.node {
hir::BinOpKind::Eq => {
if let Some(partial_eq_def_id) = self.infcx.tcx.lang_items().eq_trait()
&& self
.infcx
.type_implements_trait(partial_eq_def_id, [rhs_ty, lhs_ty], self.param_env)
.must_apply_modulo_regions()
{
let sm = self.tcx.sess.source_map();
if let Ok(rhs_snippet) = sm.span_to_snippet(rhs_expr.span)
&& let Ok(lhs_snippet) = sm.span_to_snippet(lhs_expr.span)
{
err.note(format!("`{rhs_ty}` implements `PartialEq<{lhs_ty}>`"));
err.multipart_suggestion(
"consider swapping the equality",
vec![(lhs_expr.span, rhs_snippet), (rhs_expr.span, lhs_snippet)],
Applicability::MaybeIncorrect,
);
}
}
if let Some(partial_eq_def_id) = self.infcx.tcx.lang_items().eq_trait()
&& self
.infcx
.type_implements_trait(partial_eq_def_id, [rhs_ty, lhs_ty], self.param_env)
.must_apply_modulo_regions()
{
let sm = self.tcx.sess.source_map();
if let Ok(rhs_snippet) = sm.span_to_snippet(rhs_expr.span)
&& let Ok(lhs_snippet) = sm.span_to_snippet(lhs_expr.span)
{
err.note(format!("`{rhs_ty}` implements `PartialEq<{lhs_ty}>`"));
err.multipart_suggestion(
"consider swapping the equality",
vec![(lhs_expr.span, rhs_snippet), (rhs_expr.span, lhs_snippet)],
Applicability::MaybeIncorrect,
);
}
_ => {}
}
}
}

View file

@ -4,15 +4,15 @@ use rustc_data_structures::packed::Pu128;
use rustc_errors::codes::*;
use rustc_errors::{Applicability, Diag, struct_span_code_err};
use rustc_infer::traits::ObligationCauseCode;
use rustc_middle::bug;
use rustc_middle::ty::adjustment::{
Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability,
};
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::{self, IsSuggestable, Ty, TyCtxt, TypeVisitableExt};
use rustc_middle::{bug, span_bug};
use rustc_session::errors::ExprParenthesesNeeded;
use rustc_span::source_map::Spanned;
use rustc_span::{Ident, Span, sym};
use rustc_span::{Ident, Span, Symbol, sym};
use rustc_trait_selection::infer::InferCtxtExt;
use rustc_trait_selection::traits::{FulfillmentError, Obligation, ObligationCtxt};
use tracing::debug;
@ -24,24 +24,27 @@ use crate::Expectation;
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
/// Checks a `a <op>= b`
pub(crate) fn check_expr_binop_assign(
pub(crate) fn check_expr_assign_op(
&self,
expr: &'tcx hir::Expr<'tcx>,
op: hir::BinOp,
op: hir::AssignOp,
lhs: &'tcx hir::Expr<'tcx>,
rhs: &'tcx hir::Expr<'tcx>,
expected: Expectation<'tcx>,
) -> Ty<'tcx> {
let (lhs_ty, rhs_ty, return_ty) =
self.check_overloaded_binop(expr, lhs, rhs, op, IsAssign::Yes, expected);
self.check_overloaded_binop(expr, lhs, rhs, Op::AssignOp(op), expected);
let ty =
if !lhs_ty.is_ty_var() && !rhs_ty.is_ty_var() && is_builtin_binop(lhs_ty, rhs_ty, op) {
self.enforce_builtin_binop_types(lhs.span, lhs_ty, rhs.span, rhs_ty, op);
self.tcx.types.unit
} else {
return_ty
};
let category = BinOpCategory::from(op.node);
let ty = if !lhs_ty.is_ty_var()
&& !rhs_ty.is_ty_var()
&& is_builtin_binop(lhs_ty, rhs_ty, category)
{
self.enforce_builtin_binop_types(lhs.span, lhs_ty, rhs.span, rhs_ty, category);
self.tcx.types.unit
} else {
return_ty
};
self.check_lhs_assignable(lhs, E0067, op.span, |err| {
if let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) {
@ -49,7 +52,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.lookup_op_method(
(lhs, lhs_deref_ty),
Some((rhs, rhs_ty)),
Op::Binary(op, IsAssign::Yes),
lang_item_for_binop(self.tcx, Op::AssignOp(op)),
op.span,
expected,
)
.is_ok()
@ -60,7 +64,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.lookup_op_method(
(lhs, lhs_ty),
Some((rhs, rhs_ty)),
Op::Binary(op, IsAssign::Yes),
lang_item_for_binop(self.tcx, Op::AssignOp(op)),
op.span,
expected,
)
.is_err()
@ -98,7 +103,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
expr.hir_id, expr, op, lhs_expr, rhs_expr
);
match BinOpCategory::from(op) {
match BinOpCategory::from(op.node) {
BinOpCategory::Shortcircuit => {
// && and || are a simple case.
self.check_expr_coercible_to_type(lhs_expr, tcx.types.bool, None);
@ -114,14 +119,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Otherwise, we always treat operators as if they are
// overloaded. This is the way to be most flexible w/r/t
// types that get inferred.
let (lhs_ty, rhs_ty, return_ty) = self.check_overloaded_binop(
expr,
lhs_expr,
rhs_expr,
op,
IsAssign::No,
expected,
);
let (lhs_ty, rhs_ty, return_ty) =
self.check_overloaded_binop(expr, lhs_expr, rhs_expr, Op::BinOp(op), expected);
// Supply type inference hints if relevant. Probably these
// hints should be enforced during select as part of the
@ -135,16 +134,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// deduce that the result type should be `u32`, even
// though we don't know yet what type 2 has and hence
// can't pin this down to a specific impl.
let category = BinOpCategory::from(op.node);
if !lhs_ty.is_ty_var()
&& !rhs_ty.is_ty_var()
&& is_builtin_binop(lhs_ty, rhs_ty, op)
&& is_builtin_binop(lhs_ty, rhs_ty, category)
{
let builtin_return_ty = self.enforce_builtin_binop_types(
lhs_expr.span,
lhs_ty,
rhs_expr.span,
rhs_ty,
op,
category,
);
self.demand_eqtype(expr.span, builtin_return_ty, return_ty);
builtin_return_ty
@ -161,16 +161,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
lhs_ty: Ty<'tcx>,
rhs_span: Span,
rhs_ty: Ty<'tcx>,
op: hir::BinOp,
category: BinOpCategory,
) -> Ty<'tcx> {
debug_assert!(is_builtin_binop(lhs_ty, rhs_ty, op));
debug_assert!(is_builtin_binop(lhs_ty, rhs_ty, category));
// Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work.
// (See https://github.com/rust-lang/rust/issues/57447.)
let (lhs_ty, rhs_ty) = (deref_ty_if_possible(lhs_ty), deref_ty_if_possible(rhs_ty));
let tcx = self.tcx;
match BinOpCategory::from(op) {
match category {
BinOpCategory::Shortcircuit => {
self.demand_suptype(lhs_span, tcx.types.bool, lhs_ty);
self.demand_suptype(rhs_span, tcx.types.bool, rhs_ty);
@ -201,17 +201,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
expr: &'tcx hir::Expr<'tcx>,
lhs_expr: &'tcx hir::Expr<'tcx>,
rhs_expr: &'tcx hir::Expr<'tcx>,
op: hir::BinOp,
is_assign: IsAssign,
op: Op,
expected: Expectation<'tcx>,
) -> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>) {
debug!(
"check_overloaded_binop(expr.hir_id={}, op={:?}, is_assign={:?})",
expr.hir_id, op, is_assign
);
debug!("check_overloaded_binop(expr.hir_id={}, op={:?})", expr.hir_id, op);
let lhs_ty = match is_assign {
IsAssign::No => {
let lhs_ty = match op {
Op::BinOp(_) => {
// Find a suitable supertype of the LHS expression's type, by coercing to
// a type variable, to pass as the `Self` to the trait, avoiding invariant
// trait matching creating lifetime constraints that are too strict.
@ -221,7 +217,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let fresh_var = self.next_ty_var(lhs_expr.span);
self.demand_coerce(lhs_expr, lhs_ty, fresh_var, Some(rhs_expr), AllowTwoPhase::No)
}
IsAssign::Yes => {
Op::AssignOp(_) => {
// rust-lang/rust#52126: We have to use strict
// equivalence on the LHS of an assign-op like `+=`;
// overwritten or mutably-borrowed places cannot be
@ -242,7 +238,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let result = self.lookup_op_method(
(lhs_expr, lhs_ty),
Some((rhs_expr, rhs_ty_var)),
Op::Binary(op, is_assign),
lang_item_for_binop(self.tcx, op),
op.span(),
expected,
);
@ -252,15 +249,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
rhs_ty_var,
Some(lhs_expr),
|err, ty| {
self.suggest_swapping_lhs_and_rhs(err, ty, lhs_ty, rhs_expr, lhs_expr, op);
if let Op::BinOp(binop) = op
&& binop.node == hir::BinOpKind::Eq
{
self.suggest_swapping_lhs_and_rhs(err, ty, lhs_ty, rhs_expr, lhs_expr);
}
},
);
let rhs_ty = self.resolve_vars_with_obligations(rhs_ty);
let return_ty = match result {
Ok(method) => {
let by_ref_binop = !op.node.is_by_value();
if is_assign == IsAssign::Yes || by_ref_binop {
let by_ref_binop = !op.is_by_value();
if matches!(op, Op::AssignOp(_)) || by_ref_binop {
if let ty::Ref(_, _, mutbl) = method.sig.inputs()[0].kind() {
let mutbl = AutoBorrowMutability::new(*mutbl, AllowTwoPhase::Yes);
let autoref = Adjustment {
@ -301,32 +302,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Ty::new_misc_error(self.tcx)
}
Err(errors) => {
let (_, trait_def_id) =
lang_item_for_op(self.tcx, Op::Binary(op, is_assign), op.span);
let (_, trait_def_id) = lang_item_for_binop(self.tcx, op);
let missing_trait = trait_def_id
.map(|def_id| with_no_trimmed_paths!(self.tcx.def_path_str(def_id)));
let mut path = None;
let lhs_ty_str = self.tcx.short_string(lhs_ty, &mut path);
let rhs_ty_str = self.tcx.short_string(rhs_ty, &mut path);
let (mut err, output_def_id) = match is_assign {
IsAssign::Yes => {
let (mut err, output_def_id) = match op {
Op::AssignOp(assign_op) => {
let s = assign_op.node.as_str();
let mut err = struct_span_code_err!(
self.dcx(),
expr.span,
E0368,
"binary assignment operation `{}=` cannot be applied to type `{}`",
op.node.as_str(),
"binary assignment operation `{}` cannot be applied to type `{}`",
s,
lhs_ty_str,
);
err.span_label(
lhs_expr.span,
format!("cannot use `{}=` on type `{}`", op.node.as_str(), lhs_ty_str),
format!("cannot use `{}` on type `{}`", s, lhs_ty_str),
);
self.note_unmet_impls_on_type(&mut err, errors, false);
(err, None)
}
IsAssign::No => {
let message = match op.node {
Op::BinOp(bin_op) => {
let message = match bin_op.node {
hir::BinOpKind::Add => {
format!("cannot add `{rhs_ty_str}` to `{lhs_ty_str}`")
}
@ -362,8 +363,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
_ => format!(
"binary operation `{}` cannot be applied to type `{}`",
op.node.as_str(),
lhs_ty_str,
bin_op.node.as_str(),
lhs_ty_str
),
};
let output_def_id = trait_def_id.and_then(|def_id| {
@ -376,7 +377,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.cloned()
});
let mut err =
struct_span_code_err!(self.dcx(), op.span, E0369, "{message}");
struct_span_code_err!(self.dcx(), bin_op.span, E0369, "{message}");
if !lhs_expr.span.eq(&rhs_expr.span) {
err.span_label(lhs_expr.span, lhs_ty_str.clone());
err.span_label(rhs_expr.span, rhs_ty_str);
@ -409,18 +410,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.lookup_op_method(
(lhs_expr, lhs_deref_ty),
Some((rhs_expr, rhs_ty)),
Op::Binary(op, is_assign),
lang_item_for_binop(self.tcx, op),
op.span(),
expected,
)
.is_ok()
{
let msg = format!(
"`{}{}` can be used on `{}` if you dereference the left-hand side",
op.node.as_str(),
match is_assign {
IsAssign::Yes => "=",
IsAssign::No => "",
},
"`{}` can be used on `{}` if you dereference the left-hand side",
op.as_str(),
self.tcx.short_string(lhs_deref_ty, err.long_ty_path()),
);
err.span_suggestion_verbose(
@ -442,14 +440,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.lookup_op_method(
(lhs_expr, lhs_adjusted_ty),
Some((rhs_expr, rhs_adjusted_ty)),
Op::Binary(op, is_assign),
lang_item_for_binop(self.tcx, op),
op.span(),
expected,
)
.is_ok()
{
let lhs = self.tcx.short_string(lhs_adjusted_ty, err.long_ty_path());
let rhs = self.tcx.short_string(rhs_adjusted_ty, err.long_ty_path());
let op = op.node.as_str();
let op = op.as_str();
err.note(format!("an implementation for `{lhs} {op} {rhs}` exists"));
if let Some(lhs_new_mutbl) = lhs_new_mutbl
@ -499,7 +498,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
self.lookup_op_method(
(lhs_expr, lhs_ty),
Some((rhs_expr, rhs_ty)),
Op::Binary(op, is_assign),
lang_item_for_binop(self.tcx, op),
op.span(),
expected,
)
.is_ok()
@ -511,13 +511,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// We should suggest `a + b` => `*a + b` if `a` is copy, and suggest
// `a += b` => `*a += b` if a is a mut ref.
if !op.span.can_be_used_for_suggestions() {
if !op.span().can_be_used_for_suggestions() {
// Suppress suggestions when lhs and rhs are not in the same span as the error
} else if is_assign == IsAssign::Yes
} else if let Op::AssignOp(_) = op
&& let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty)
{
suggest_deref_binop(&mut err, lhs_deref_ty);
} else if is_assign == IsAssign::No
} else if let Op::BinOp(_) = op
&& let ty::Ref(region, lhs_deref_ty, mutbl) = lhs_ty.kind()
{
if self.type_is_copy_modulo_regions(self.param_env, *lhs_deref_ty) {
@ -572,10 +572,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
if let Some(missing_trait) = missing_trait {
if op.node == hir::BinOpKind::Add
&& self.check_str_addition(
lhs_expr, rhs_expr, lhs_ty, rhs_ty, &mut err, is_assign, op,
)
if matches!(
op,
Op::BinOp(Spanned { node: hir::BinOpKind::Add, .. })
| Op::AssignOp(Spanned { node: hir::AssignOpKind::AddAssign, .. })
) && self
.check_str_addition(lhs_expr, rhs_expr, lhs_ty, rhs_ty, &mut err, op)
{
// This has nothing here because it means we did string
// concatenation (e.g., "Hello " + "World!"). This means
@ -592,7 +594,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.lookup_op_method(
(lhs_expr, lhs_ty),
Some((rhs_expr, rhs_ty)),
Op::Binary(op, is_assign),
lang_item_for_binop(self.tcx, op),
op.span(),
expected,
)
.unwrap_err();
@ -642,9 +645,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Suggest using `add`, `offset` or `offset_from` for pointer - {integer},
// pointer + {integer} or pointer - pointer.
if op.span.can_be_used_for_suggestions() {
match op.node {
hir::BinOpKind::Add if lhs_ty.is_raw_ptr() && rhs_ty.is_integral() => {
if op.span().can_be_used_for_suggestions() {
match op {
Op::BinOp(Spanned { node: hir::BinOpKind::Add, .. })
if lhs_ty.is_raw_ptr() && rhs_ty.is_integral() =>
{
err.multipart_suggestion(
"consider using `wrapping_add` or `add` for pointer + {integer}",
vec![
@ -657,7 +662,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Applicability::MaybeIncorrect,
);
}
hir::BinOpKind::Sub => {
Op::BinOp(Spanned { node: hir::BinOpKind::Sub, .. }) => {
if lhs_ty.is_raw_ptr() && rhs_ty.is_integral() {
err.multipart_suggestion(
"consider using `wrapping_sub` or `sub` for \
@ -713,8 +718,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
lhs_ty: Ty<'tcx>,
rhs_ty: Ty<'tcx>,
err: &mut Diag<'_>,
is_assign: IsAssign,
op: hir::BinOp,
op: Op,
) -> bool {
let str_concat_note = "string concatenation requires an owned `String` on the left";
let rm_borrow_msg = "remove the borrow to obtain an owned `String`";
@ -733,8 +737,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
r_ty.kind(), ty::Ref(_, inner_ty, _) if *inner_ty.kind() == ty::Str
)) =>
{
if let IsAssign::No = is_assign { // Do not supply this message if `&str += &str`
err.span_label(op.span, "`+` cannot be used to concatenate two `&str` strings");
if let Op::BinOp(_) = op { // Do not supply this message if `&str += &str`
err.span_label(
op.span(),
"`+` cannot be used to concatenate two `&str` strings"
);
err.note(str_concat_note);
if let hir::ExprKind::AddrOf(_, _, lhs_inner_expr) = lhs_expr.kind {
err.span_suggestion_verbose(
@ -758,11 +765,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
if (*l_ty.kind() == ty::Str || is_std_string(l_ty)) && is_std_string(rhs_ty) =>
{
err.span_label(
op.span,
op.span(),
"`+` cannot be used to concatenate a `&str` with a `String`",
);
match is_assign {
IsAssign::No => {
match op {
Op::BinOp(_) => {
let sugg_msg;
let lhs_sugg = if let hir::ExprKind::AddrOf(_, _, lhs_inner_expr) = lhs_expr.kind {
sugg_msg = "remove the borrow on the left and add one on the right";
@ -781,7 +788,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Applicability::MachineApplicable,
);
}
IsAssign::Yes => {
Op::AssignOp(_) => {
err.note(str_concat_note);
}
}
@ -799,7 +806,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
expected: Expectation<'tcx>,
) -> Ty<'tcx> {
assert!(op.is_by_value());
match self.lookup_op_method((ex, operand_ty), None, Op::Unary(op, ex.span), expected) {
match self.lookup_op_method(
(ex, operand_ty),
None,
lang_item_for_unop(self.tcx, op),
ex.span,
expected,
) {
Ok(method) => {
self.write_method_call_and_enforce_effects(ex.hir_id, ex.span, method);
method.sig.output()
@ -898,21 +911,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&self,
(lhs_expr, lhs_ty): (&'tcx hir::Expr<'tcx>, Ty<'tcx>),
opt_rhs: Option<(&'tcx hir::Expr<'tcx>, Ty<'tcx>)>,
op: Op,
(opname, trait_did): (Symbol, Option<hir::def_id::DefId>),
span: Span,
expected: Expectation<'tcx>,
) -> Result<MethodCallee<'tcx>, Vec<FulfillmentError<'tcx>>> {
let span = match op {
Op::Binary(op, _) => op.span,
Op::Unary(_, span) => span,
};
let (opname, Some(trait_did)) = lang_item_for_op(self.tcx, op, span) else {
let Some(trait_did) = trait_did else {
// Bail if the operator trait is not defined.
return Err(vec![]);
};
debug!(
"lookup_op_method(lhs_ty={:?}, op={:?}, opname={:?}, trait_did={:?})",
lhs_ty, op, opname, trait_did
"lookup_op_method(lhs_ty={:?}, opname={:?}, trait_did={:?})",
lhs_ty, opname, trait_did
);
let opname = Ident::with_dummy_span(opname);
@ -980,37 +990,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
fn lang_item_for_op(
tcx: TyCtxt<'_>,
op: Op,
span: Span,
) -> (rustc_span::Symbol, Option<hir::def_id::DefId>) {
fn lang_item_for_binop(tcx: TyCtxt<'_>, op: Op) -> (Symbol, Option<hir::def_id::DefId>) {
let lang = tcx.lang_items();
if let Op::Binary(op, IsAssign::Yes) = op {
match op.node {
hir::BinOpKind::Add => (sym::add_assign, lang.add_assign_trait()),
hir::BinOpKind::Sub => (sym::sub_assign, lang.sub_assign_trait()),
hir::BinOpKind::Mul => (sym::mul_assign, lang.mul_assign_trait()),
hir::BinOpKind::Div => (sym::div_assign, lang.div_assign_trait()),
hir::BinOpKind::Rem => (sym::rem_assign, lang.rem_assign_trait()),
hir::BinOpKind::BitXor => (sym::bitxor_assign, lang.bitxor_assign_trait()),
hir::BinOpKind::BitAnd => (sym::bitand_assign, lang.bitand_assign_trait()),
hir::BinOpKind::BitOr => (sym::bitor_assign, lang.bitor_assign_trait()),
hir::BinOpKind::Shl => (sym::shl_assign, lang.shl_assign_trait()),
hir::BinOpKind::Shr => (sym::shr_assign, lang.shr_assign_trait()),
hir::BinOpKind::Lt
| hir::BinOpKind::Le
| hir::BinOpKind::Ge
| hir::BinOpKind::Gt
| hir::BinOpKind::Eq
| hir::BinOpKind::Ne
| hir::BinOpKind::And
| hir::BinOpKind::Or => {
span_bug!(span, "impossible assignment operation: {}=", op.node.as_str())
}
}
} else if let Op::Binary(op, IsAssign::No) = op {
match op.node {
match op {
Op::AssignOp(op) => match op.node {
hir::AssignOpKind::AddAssign => (sym::add_assign, lang.add_assign_trait()),
hir::AssignOpKind::SubAssign => (sym::sub_assign, lang.sub_assign_trait()),
hir::AssignOpKind::MulAssign => (sym::mul_assign, lang.mul_assign_trait()),
hir::AssignOpKind::DivAssign => (sym::div_assign, lang.div_assign_trait()),
hir::AssignOpKind::RemAssign => (sym::rem_assign, lang.rem_assign_trait()),
hir::AssignOpKind::BitXorAssign => (sym::bitxor_assign, lang.bitxor_assign_trait()),
hir::AssignOpKind::BitAndAssign => (sym::bitand_assign, lang.bitand_assign_trait()),
hir::AssignOpKind::BitOrAssign => (sym::bitor_assign, lang.bitor_assign_trait()),
hir::AssignOpKind::ShlAssign => (sym::shl_assign, lang.shl_assign_trait()),
hir::AssignOpKind::ShrAssign => (sym::shr_assign, lang.shr_assign_trait()),
},
Op::BinOp(op) => match op.node {
hir::BinOpKind::Add => (sym::add, lang.add_trait()),
hir::BinOpKind::Sub => (sym::sub, lang.sub_trait()),
hir::BinOpKind::Mul => (sym::mul, lang.mul_trait()),
@ -1028,20 +1023,24 @@ fn lang_item_for_op(
hir::BinOpKind::Eq => (sym::eq, lang.eq_trait()),
hir::BinOpKind::Ne => (sym::ne, lang.eq_trait()),
hir::BinOpKind::And | hir::BinOpKind::Or => {
span_bug!(span, "&& and || are not overloadable")
bug!("&& and || are not overloadable")
}
}
} else if let Op::Unary(hir::UnOp::Not, _) = op {
(sym::not, lang.not_trait())
} else if let Op::Unary(hir::UnOp::Neg, _) = op {
(sym::neg, lang.neg_trait())
} else {
bug!("lookup_op_method: op not supported: {:?}", op)
},
}
}
fn lang_item_for_unop(tcx: TyCtxt<'_>, op: hir::UnOp) -> (Symbol, Option<hir::def_id::DefId>) {
let lang = tcx.lang_items();
match op {
hir::UnOp::Not => (sym::not, lang.not_trait()),
hir::UnOp::Neg => (sym::neg, lang.neg_trait()),
hir::UnOp::Deref => bug!("Deref is not overloadable"),
}
}
// Binary operator categories. These categories summarize the behavior
// with respect to the builtin operations supported.
#[derive(Clone, Copy)]
enum BinOpCategory {
/// &&, || -- cannot be overridden
Shortcircuit,
@ -1063,44 +1062,58 @@ enum BinOpCategory {
Comparison,
}
impl BinOpCategory {
fn from(op: hir::BinOp) -> BinOpCategory {
match op.node {
hir::BinOpKind::Shl | hir::BinOpKind::Shr => BinOpCategory::Shift,
hir::BinOpKind::Add
| hir::BinOpKind::Sub
| hir::BinOpKind::Mul
| hir::BinOpKind::Div
| hir::BinOpKind::Rem => BinOpCategory::Math,
hir::BinOpKind::BitXor | hir::BinOpKind::BitAnd | hir::BinOpKind::BitOr => {
BinOpCategory::Bitwise
}
hir::BinOpKind::Eq
| hir::BinOpKind::Ne
| hir::BinOpKind::Lt
| hir::BinOpKind::Le
| hir::BinOpKind::Ge
| hir::BinOpKind::Gt => BinOpCategory::Comparison,
hir::BinOpKind::And | hir::BinOpKind::Or => BinOpCategory::Shortcircuit,
impl From<hir::BinOpKind> for BinOpCategory {
fn from(op: hir::BinOpKind) -> BinOpCategory {
use hir::BinOpKind::*;
match op {
Shl | Shr => BinOpCategory::Shift,
Add | Sub | Mul | Div | Rem => BinOpCategory::Math,
BitXor | BitAnd | BitOr => BinOpCategory::Bitwise,
Eq | Ne | Lt | Le | Ge | Gt => BinOpCategory::Comparison,
And | Or => BinOpCategory::Shortcircuit,
}
}
}
/// Whether the binary operation is an assignment (`a += b`), or not (`a + b`)
#[derive(Clone, Copy, Debug, PartialEq)]
enum IsAssign {
No,
Yes,
impl From<hir::AssignOpKind> for BinOpCategory {
fn from(op: hir::AssignOpKind) -> BinOpCategory {
use hir::AssignOpKind::*;
match op {
ShlAssign | ShrAssign => BinOpCategory::Shift,
AddAssign | SubAssign | MulAssign | DivAssign | RemAssign => BinOpCategory::Math,
BitXorAssign | BitAndAssign | BitOrAssign => BinOpCategory::Bitwise,
}
}
}
#[derive(Clone, Copy, Debug)]
/// An assignment op (e.g. `a += b`), or a binary op (e.g. `a + b`).
#[derive(Clone, Copy, Debug, PartialEq)]
enum Op {
Binary(hir::BinOp, IsAssign),
Unary(hir::UnOp, Span),
BinOp(hir::BinOp),
AssignOp(hir::AssignOp),
}
impl Op {
fn span(&self) -> Span {
match self {
Op::BinOp(op) => op.span,
Op::AssignOp(op) => op.span,
}
}
fn as_str(&self) -> &'static str {
match self {
Op::BinOp(op) => op.node.as_str(),
Op::AssignOp(op) => op.node.as_str(),
}
}
fn is_by_value(&self) -> bool {
match self {
Op::BinOp(op) => op.node.is_by_value(),
Op::AssignOp(op) => op.node.is_by_value(),
}
}
}
/// Dereferences a single level of immutable referencing.
@ -1127,27 +1140,24 @@ fn deref_ty_if_possible(ty: Ty<'_>) -> Ty<'_> {
/// Reason #2 is the killer. I tried for a while to always use
/// overloaded logic and just check the types in constants/codegen after
/// the fact, and it worked fine, except for SIMD types. -nmatsakis
fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, op: hir::BinOp) -> bool {
fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, category: BinOpCategory) -> bool {
// Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work.
// (See https://github.com/rust-lang/rust/issues/57447.)
let (lhs, rhs) = (deref_ty_if_possible(lhs), deref_ty_if_possible(rhs));
match BinOpCategory::from(op) {
match category.into() {
BinOpCategory::Shortcircuit => true,
BinOpCategory::Shift => {
lhs.references_error()
|| rhs.references_error()
|| lhs.is_integral() && rhs.is_integral()
}
BinOpCategory::Math => {
lhs.references_error()
|| rhs.references_error()
|| lhs.is_integral() && rhs.is_integral()
|| lhs.is_floating_point() && rhs.is_floating_point()
}
BinOpCategory::Bitwise => {
lhs.references_error()
|| rhs.references_error()
@ -1155,7 +1165,6 @@ fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, op: hir::BinOp) -> bool
|| lhs.is_floating_point() && rhs.is_floating_point()
|| lhs.is_bool() && rhs.is_bool()
}
BinOpCategory::Comparison => {
lhs.references_error() || rhs.references_error() || lhs.is_scalar() && rhs.is_scalar()
}

View file

@ -85,7 +85,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
/// Intermediate format to store the hir_id pointing to the use that resulted in the
/// corresponding place being captured and a String which contains the captured value's
/// name (i.e: a.b.c)
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
enum UpvarMigrationInfo {
/// We previously captured all of `x`, but now we capture some sub-path.
CapturingPrecise { source_expr: Option<HirId>, var_name: String },
@ -1396,14 +1396,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
FxIndexSet::default()
};
// Combine all the captures responsible for needing migrations into one HashSet
// Combine all the captures responsible for needing migrations into one IndexSet
let mut capture_diagnostic = drop_reorder_diagnostic.clone();
for key in auto_trait_diagnostic.keys() {
capture_diagnostic.insert(key.clone());
}
let mut capture_diagnostic = capture_diagnostic.into_iter().collect::<Vec<_>>();
capture_diagnostic.sort();
capture_diagnostic.sort_by_cached_key(|info| match info {
UpvarMigrationInfo::CapturingPrecise { source_expr: _, var_name } => {
(0, Some(var_name.clone()))
}
UpvarMigrationInfo::CapturingNothing { use_span: _ } => (1, None),
});
for captures_info in capture_diagnostic {
// Get the auto trait reasons of why migration is needed because of that capture, if there are any
let capture_trait_reasons =
@ -2323,8 +2328,9 @@ fn should_do_rust_2021_incompatible_closure_captures_analysis(
return false;
}
let (level, _) =
tcx.lint_level_at_node(lint::builtin::RUST_2021_INCOMPATIBLE_CLOSURE_CAPTURES, closure_id);
let level = tcx
.lint_level_at_node(lint::builtin::RUST_2021_INCOMPATIBLE_CLOSURE_CAPTURES, closure_id)
.level;
!matches!(level, lint::Level::Allow)
}

View file

@ -12,10 +12,12 @@ use rustc_middle::span_bug;
use rustc_middle::traits::ObligationCause;
use rustc_middle::ty::adjustment::{Adjust, Adjustment, PointerCoercion};
use rustc_middle::ty::{
self, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitableExt, fold_regions,
self, DefiningScopeKind, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable,
TypeVisitableExt, fold_regions,
};
use rustc_span::{Span, sym};
use rustc_trait_selection::error_reporting::infer::need_type_info::TypeAnnotationNeeded;
use rustc_trait_selection::opaque_types::check_opaque_type_parameter_valid;
use rustc_trait_selection::solve;
use tracing::{debug, instrument};
@ -157,7 +159,7 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
self.typeck_results.node_args_mut().remove(e.hir_id);
}
}
hir::ExprKind::Binary(ref op, lhs, rhs) | hir::ExprKind::AssignOp(ref op, lhs, rhs) => {
hir::ExprKind::Binary(ref op, lhs, rhs) => {
let lhs_ty = self.typeck_results.node_type(lhs.hir_id);
let rhs_ty = self.typeck_results.node_type(rhs.hir_id);
@ -165,25 +167,27 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
self.typeck_results.type_dependent_defs_mut().remove(e.hir_id);
self.typeck_results.node_args_mut().remove(e.hir_id);
match e.kind {
hir::ExprKind::Binary(..) => {
if !op.node.is_by_value() {
let mut adjustments = self.typeck_results.adjustments_mut();
if let Some(a) = adjustments.get_mut(lhs.hir_id) {
a.pop();
}
if let Some(a) = adjustments.get_mut(rhs.hir_id) {
a.pop();
}
}
}
hir::ExprKind::AssignOp(..)
if let Some(a) =
self.typeck_results.adjustments_mut().get_mut(lhs.hir_id) =>
{
if !op.node.is_by_value() {
let mut adjustments = self.typeck_results.adjustments_mut();
if let Some(a) = adjustments.get_mut(lhs.hir_id) {
a.pop();
}
_ => {}
if let Some(a) = adjustments.get_mut(rhs.hir_id) {
a.pop();
}
}
}
}
hir::ExprKind::AssignOp(_, lhs, rhs) => {
let lhs_ty = self.typeck_results.node_type(lhs.hir_id);
let rhs_ty = self.typeck_results.node_type(rhs.hir_id);
if lhs_ty.is_scalar() && rhs_ty.is_scalar() {
self.typeck_results.type_dependent_defs_mut().remove(e.hir_id);
self.typeck_results.node_args_mut().remove(e.hir_id);
if let Some(a) = self.typeck_results.adjustments_mut().get_mut(lhs.hir_id) {
a.pop();
}
}
}
@ -553,6 +557,7 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
#[instrument(skip(self), level = "debug")]
fn visit_opaque_types(&mut self) {
let tcx = self.tcx();
// We clone the opaques instead of stealing them here as they are still used for
// normalization in the next generation trait solver.
//
@ -575,16 +580,46 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
}
}
// Here we only detect impl trait definition conflicts when they
// are equal modulo regions.
if let Some(last_opaque_ty) =
self.typeck_results.concrete_opaque_types.insert(opaque_type_key, hidden_type)
&& last_opaque_ty.ty != hidden_type.ty
if let Err(guar) = check_opaque_type_parameter_valid(
&self.fcx,
opaque_type_key,
hidden_type.span,
DefiningScopeKind::HirTypeck,
) {
self.typeck_results
.concrete_opaque_types
.insert(opaque_type_key.def_id, ty::OpaqueHiddenType::new_error(tcx, guar));
}
let hidden_type = hidden_type.remap_generic_params_to_declaration_params(
opaque_type_key,
tcx,
DefiningScopeKind::HirTypeck,
);
if let Some(prev) = self
.typeck_results
.concrete_opaque_types
.insert(opaque_type_key.def_id, hidden_type)
{
assert!(!self.fcx.next_trait_solver());
if let Ok(d) = hidden_type.build_mismatch_error(&last_opaque_ty, self.tcx()) {
d.emit();
let entry = &mut self
.typeck_results
.concrete_opaque_types
.get_mut(&opaque_type_key.def_id)
.unwrap();
if prev.ty != hidden_type.ty {
if let Some(guar) = self.typeck_results.tainted_by_errors {
entry.ty = Ty::new_error(tcx, guar);
} else {
let (Ok(guar) | Err(guar)) =
prev.build_mismatch_error(&hidden_type, tcx).map(|d| d.emit());
entry.ty = Ty::new_error(tcx, guar);
}
}
// Pick a better span if there is one.
// FIXME(oli-obk): collect multiple spans for better diagnostics down the road.
entry.span = prev.span.substitute_dummy(hidden_type.span);
}
}
}

View file

@ -967,7 +967,8 @@ impl<'tcx> InferCtxt<'tcx> {
pub fn can_define_opaque_ty(&self, id: impl Into<DefId>) -> bool {
debug_assert!(!self.next_trait_solver());
match self.typing_mode() {
TypingMode::Analysis { defining_opaque_types } => {
TypingMode::Analysis { defining_opaque_types }
| TypingMode::Borrowck { defining_opaque_types } => {
id.into().as_local().is_some_and(|def_id| defining_opaque_types.contains(&def_id))
}
// FIXME(#132279): This function is quite weird in post-analysis
@ -1261,7 +1262,8 @@ impl<'tcx> InferCtxt<'tcx> {
// to handle them without proper canonicalization. This means we may cause cycle
// errors and fail to reveal opaques while inside of bodies. We should rename this
// function and require explicit comments on all use-sites in the future.
ty::TypingMode::Analysis { defining_opaque_types: _ } => {
ty::TypingMode::Analysis { defining_opaque_types: _ }
| ty::TypingMode::Borrowck { defining_opaque_types: _ } => {
TypingMode::non_body_analysis()
}
mode @ (ty::TypingMode::Coherence

View file

@ -12,7 +12,7 @@ use rustc_middle::ty::{
use rustc_span::Span;
use tracing::{debug, instrument};
use super::DefineOpaqueTypes;
use super::{DefineOpaqueTypes, RegionVariableOrigin};
use crate::errors::OpaqueHiddenTypeDiag;
use crate::infer::{InferCtxt, InferOk};
use crate::traits::{self, Obligation, PredicateObligations};
@ -221,6 +221,7 @@ impl<'tcx> InferCtxt<'tcx> {
hidden_ty: Ty<'tcx>,
goals: &mut Vec<Goal<'tcx, ty::Predicate<'tcx>>>,
) -> Result<(), TypeError<'tcx>> {
let tcx = self.tcx;
// Ideally, we'd get the span where *this specific `ty` came
// from*, but right now we just use the span from the overall
// value being folded. In simple cases like `-> impl Foo`,
@ -231,7 +232,7 @@ impl<'tcx> InferCtxt<'tcx> {
// During intercrate we do not define opaque types but instead always
// force ambiguity unless the hidden type is known to not implement
// our trait.
goals.push(Goal::new(self.tcx, param_env, ty::PredicateKind::Ambiguous));
goals.push(Goal::new(tcx, param_env, ty::PredicateKind::Ambiguous));
}
ty::TypingMode::Analysis { .. } => {
let prev = self
@ -249,6 +250,36 @@ impl<'tcx> InferCtxt<'tcx> {
);
}
}
ty::TypingMode::Borrowck { .. } => {
let prev = self
.inner
.borrow_mut()
.opaque_types()
.register(opaque_type_key, OpaqueHiddenType { ty: hidden_ty, span });
// We either equate the new hidden type with the previous entry or with the type
// inferred by HIR typeck.
let actual = prev.unwrap_or_else(|| {
let actual = tcx
.type_of_opaque_hir_typeck(opaque_type_key.def_id)
.instantiate(self.tcx, opaque_type_key.args);
let actual = ty::fold_regions(tcx, actual, |re, _dbi| match re.kind() {
ty::ReErased => {
self.next_region_var(RegionVariableOrigin::MiscVariable(span))
}
_ => re,
});
actual
});
goals.extend(
self.at(&ObligationCause::dummy_with_span(span), param_env)
.eq(DefineOpaqueTypes::Yes, hidden_ty, actual)?
.obligations
.into_iter()
.map(|obligation| obligation.as_goal()),
);
}
mode @ (ty::TypingMode::PostBorrowckAnalysis { .. } | ty::TypingMode::PostAnalysis) => {
bug!("insert hidden type in {mode:?}")
}

View file

@ -204,6 +204,14 @@ pub(crate) fn parse_check_cfg(dcx: DiagCtxtHandle<'_>, specs: Vec<String>) -> Ch
error!("`cfg()` names cannot be after values");
}
names.push(ident);
} else if let Some(boolean) = arg.boolean_literal() {
if values_specified {
error!("`cfg()` names cannot be after values");
}
names.push(rustc_span::Ident::new(
if boolean { rustc_span::kw::True } else { rustc_span::kw::False },
arg.span(),
));
} else if arg.has_name(sym::any)
&& let Some(args) = arg.meta_item_list()
{

View file

@ -976,7 +976,7 @@ fn run_required_analyses(tcx: TyCtxt<'_>) {
tcx.par_hir_body_owners(|def_id| {
if tcx.is_coroutine(def_id.to_def_id()) {
tcx.ensure_ok().mir_coroutine_witnesses(def_id);
tcx.ensure_ok().check_coroutine_obligations(
let _ = tcx.ensure_ok().check_coroutine_obligations(
tcx.typeck_root_def_id(def_id.to_def_id()).expect_local(),
);
// Eagerly check the unsubstituted layout for cycles.

View file

@ -853,6 +853,7 @@ fn test_unstable_options_tracking_hash() {
tracked!(sanitizer_cfi_generalize_pointers, Some(true));
tracked!(sanitizer_cfi_normalize_integers, Some(true));
tracked!(sanitizer_dataflow_abilist, vec![String::from("/rustc/abc")]);
tracked!(sanitizer_kcfi_arity, Some(true));
tracked!(sanitizer_memory_track_origins, 2);
tracked!(sanitizer_recover, SanitizerSet::ADDRESS);
tracked!(saturating_float_casts, Some(true));

View file

@ -26,7 +26,6 @@
// tidy-alphabetical-end
mod cursor;
pub mod unescape;
#[cfg(test)]
mod tests;

View file

@ -1,438 +0,0 @@
//! Utilities for validating string and char literals and turning them into
//! values they represent.
use std::ops::Range;
use std::str::Chars;
use Mode::*;
#[cfg(test)]
mod tests;
/// Errors and warnings that can occur during string unescaping. They mostly
/// relate to malformed escape sequences, but there are a few that are about
/// other problems.
#[derive(Debug, PartialEq, Eq)]
pub enum EscapeError {
/// Expected 1 char, but 0 were found.
ZeroChars,
/// Expected 1 char, but more than 1 were found.
MoreThanOneChar,
/// Escaped '\' character without continuation.
LoneSlash,
/// Invalid escape character (e.g. '\z').
InvalidEscape,
/// Raw '\r' encountered.
BareCarriageReturn,
/// Raw '\r' encountered in raw string.
BareCarriageReturnInRawString,
/// Unescaped character that was expected to be escaped (e.g. raw '\t').
EscapeOnlyChar,
/// Numeric character escape is too short (e.g. '\x1').
TooShortHexEscape,
/// Invalid character in numeric escape (e.g. '\xz')
InvalidCharInHexEscape,
/// Character code in numeric escape is non-ascii (e.g. '\xFF').
OutOfRangeHexEscape,
/// '\u' not followed by '{'.
NoBraceInUnicodeEscape,
/// Non-hexadecimal value in '\u{..}'.
InvalidCharInUnicodeEscape,
/// '\u{}'
EmptyUnicodeEscape,
/// No closing brace in '\u{..}', e.g. '\u{12'.
UnclosedUnicodeEscape,
/// '\u{_12}'
LeadingUnderscoreUnicodeEscape,
/// More than 6 characters in '\u{..}', e.g. '\u{10FFFF_FF}'
OverlongUnicodeEscape,
/// Invalid in-bound unicode character code, e.g. '\u{DFFF}'.
LoneSurrogateUnicodeEscape,
/// Out of bounds unicode character code, e.g. '\u{FFFFFF}'.
OutOfRangeUnicodeEscape,
/// Unicode escape code in byte literal.
UnicodeEscapeInByte,
/// Non-ascii character in byte literal, byte string literal, or raw byte string literal.
NonAsciiCharInByte,
// `\0` in a C string literal.
NulInCStr,
/// After a line ending with '\', the next line contains whitespace
/// characters that are not skipped.
UnskippedWhitespaceWarning,
/// After a line ending with '\', multiple lines are skipped.
MultipleSkippedLinesWarning,
}
impl EscapeError {
/// Returns true for actual errors, as opposed to warnings.
pub fn is_fatal(&self) -> bool {
!matches!(
self,
EscapeError::UnskippedWhitespaceWarning | EscapeError::MultipleSkippedLinesWarning
)
}
}
/// Takes the contents of a unicode-only (non-mixed-utf8) literal (without
/// quotes) and produces a sequence of escaped characters or errors.
///
/// Values are returned by invoking `callback`. For `Char` and `Byte` modes,
/// the callback will be called exactly once.
pub fn unescape_unicode<F>(src: &str, mode: Mode, callback: &mut F)
where
F: FnMut(Range<usize>, Result<char, EscapeError>),
{
match mode {
Char | Byte => {
let mut chars = src.chars();
let res = unescape_char_or_byte(&mut chars, mode);
callback(0..(src.len() - chars.as_str().len()), res);
}
Str | ByteStr => unescape_non_raw_common(src, mode, callback),
RawStr | RawByteStr => check_raw_common(src, mode, callback),
RawCStr => check_raw_common(src, mode, &mut |r, mut result| {
if let Ok('\0') = result {
result = Err(EscapeError::NulInCStr);
}
callback(r, result)
}),
CStr => unreachable!(),
}
}
/// Used for mixed utf8 string literals, i.e. those that allow both unicode
/// chars and high bytes.
pub enum MixedUnit {
/// Used for ASCII chars (written directly or via `\x00`..`\x7f` escapes)
/// and Unicode chars (written directly or via `\u` escapes).
///
/// For example, if '¥' appears in a string it is represented here as
/// `MixedUnit::Char('¥')`, and it will be appended to the relevant byte
/// string as the two-byte UTF-8 sequence `[0xc2, 0xa5]`
Char(char),
/// Used for high bytes (`\x80`..`\xff`).
///
/// For example, if `\xa5` appears in a string it is represented here as
/// `MixedUnit::HighByte(0xa5)`, and it will be appended to the relevant
/// byte string as the single byte `0xa5`.
HighByte(u8),
}
impl From<char> for MixedUnit {
fn from(c: char) -> Self {
MixedUnit::Char(c)
}
}
impl From<u8> for MixedUnit {
fn from(n: u8) -> Self {
if n.is_ascii() { MixedUnit::Char(n as char) } else { MixedUnit::HighByte(n) }
}
}
/// Takes the contents of a mixed-utf8 literal (without quotes) and produces
/// a sequence of escaped characters or errors.
///
/// Values are returned by invoking `callback`.
pub fn unescape_mixed<F>(src: &str, mode: Mode, callback: &mut F)
where
F: FnMut(Range<usize>, Result<MixedUnit, EscapeError>),
{
match mode {
CStr => unescape_non_raw_common(src, mode, &mut |r, mut result| {
if let Ok(MixedUnit::Char('\0')) = result {
result = Err(EscapeError::NulInCStr);
}
callback(r, result)
}),
Char | Byte | Str | RawStr | ByteStr | RawByteStr | RawCStr => unreachable!(),
}
}
/// Takes a contents of a char literal (without quotes), and returns an
/// unescaped char or an error.
pub fn unescape_char(src: &str) -> Result<char, EscapeError> {
unescape_char_or_byte(&mut src.chars(), Char)
}
/// Takes a contents of a byte literal (without quotes), and returns an
/// unescaped byte or an error.
pub fn unescape_byte(src: &str) -> Result<u8, EscapeError> {
unescape_char_or_byte(&mut src.chars(), Byte).map(byte_from_char)
}
/// What kind of literal do we parse.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Mode {
Char,
Byte,
Str,
RawStr,
ByteStr,
RawByteStr,
CStr,
RawCStr,
}
impl Mode {
pub fn in_double_quotes(self) -> bool {
match self {
Str | RawStr | ByteStr | RawByteStr | CStr | RawCStr => true,
Char | Byte => false,
}
}
/// Are `\x80`..`\xff` allowed?
fn allow_high_bytes(self) -> bool {
match self {
Char | Str => false,
Byte | ByteStr | CStr => true,
RawStr | RawByteStr | RawCStr => unreachable!(),
}
}
/// Are unicode (non-ASCII) chars allowed?
#[inline]
fn allow_unicode_chars(self) -> bool {
match self {
Byte | ByteStr | RawByteStr => false,
Char | Str | RawStr | CStr | RawCStr => true,
}
}
/// Are unicode escapes (`\u`) allowed?
fn allow_unicode_escapes(self) -> bool {
match self {
Byte | ByteStr => false,
Char | Str | CStr => true,
RawByteStr | RawStr | RawCStr => unreachable!(),
}
}
pub fn prefix_noraw(self) -> &'static str {
match self {
Char | Str | RawStr => "",
Byte | ByteStr | RawByteStr => "b",
CStr | RawCStr => "c",
}
}
}
fn scan_escape<T: From<char> + From<u8>>(
chars: &mut Chars<'_>,
mode: Mode,
) -> Result<T, EscapeError> {
// Previous character was '\\', unescape what follows.
let res: char = match chars.next().ok_or(EscapeError::LoneSlash)? {
'"' => '"',
'n' => '\n',
'r' => '\r',
't' => '\t',
'\\' => '\\',
'\'' => '\'',
'0' => '\0',
'x' => {
// Parse hexadecimal character code.
let hi = chars.next().ok_or(EscapeError::TooShortHexEscape)?;
let hi = hi.to_digit(16).ok_or(EscapeError::InvalidCharInHexEscape)?;
let lo = chars.next().ok_or(EscapeError::TooShortHexEscape)?;
let lo = lo.to_digit(16).ok_or(EscapeError::InvalidCharInHexEscape)?;
let value = (hi * 16 + lo) as u8;
return if !mode.allow_high_bytes() && !value.is_ascii() {
Err(EscapeError::OutOfRangeHexEscape)
} else {
// This may be a high byte, but that will only happen if `T` is
// `MixedUnit`, because of the `allow_high_bytes` check above.
Ok(T::from(value))
};
}
'u' => return scan_unicode(chars, mode.allow_unicode_escapes()).map(T::from),
_ => return Err(EscapeError::InvalidEscape),
};
Ok(T::from(res))
}
fn scan_unicode(chars: &mut Chars<'_>, allow_unicode_escapes: bool) -> Result<char, EscapeError> {
// We've parsed '\u', now we have to parse '{..}'.
if chars.next() != Some('{') {
return Err(EscapeError::NoBraceInUnicodeEscape);
}
// First character must be a hexadecimal digit.
let mut n_digits = 1;
let mut value: u32 = match chars.next().ok_or(EscapeError::UnclosedUnicodeEscape)? {
'_' => return Err(EscapeError::LeadingUnderscoreUnicodeEscape),
'}' => return Err(EscapeError::EmptyUnicodeEscape),
c => c.to_digit(16).ok_or(EscapeError::InvalidCharInUnicodeEscape)?,
};
// First character is valid, now parse the rest of the number
// and closing brace.
loop {
match chars.next() {
None => return Err(EscapeError::UnclosedUnicodeEscape),
Some('_') => continue,
Some('}') => {
if n_digits > 6 {
return Err(EscapeError::OverlongUnicodeEscape);
}
// Incorrect syntax has higher priority for error reporting
// than unallowed value for a literal.
if !allow_unicode_escapes {
return Err(EscapeError::UnicodeEscapeInByte);
}
break std::char::from_u32(value).ok_or({
if value > 0x10FFFF {
EscapeError::OutOfRangeUnicodeEscape
} else {
EscapeError::LoneSurrogateUnicodeEscape
}
});
}
Some(c) => {
let digit: u32 = c.to_digit(16).ok_or(EscapeError::InvalidCharInUnicodeEscape)?;
n_digits += 1;
if n_digits > 6 {
// Stop updating value since we're sure that it's incorrect already.
continue;
}
value = value * 16 + digit;
}
};
}
}
#[inline]
fn ascii_check(c: char, allow_unicode_chars: bool) -> Result<char, EscapeError> {
if allow_unicode_chars || c.is_ascii() { Ok(c) } else { Err(EscapeError::NonAsciiCharInByte) }
}
fn unescape_char_or_byte(chars: &mut Chars<'_>, mode: Mode) -> Result<char, EscapeError> {
let c = chars.next().ok_or(EscapeError::ZeroChars)?;
let res = match c {
'\\' => scan_escape(chars, mode),
'\n' | '\t' | '\'' => Err(EscapeError::EscapeOnlyChar),
'\r' => Err(EscapeError::BareCarriageReturn),
_ => ascii_check(c, mode.allow_unicode_chars()),
}?;
if chars.next().is_some() {
return Err(EscapeError::MoreThanOneChar);
}
Ok(res)
}
/// Takes a contents of a string literal (without quotes) and produces a
/// sequence of escaped characters or errors.
fn unescape_non_raw_common<F, T: From<char> + From<u8>>(src: &str, mode: Mode, callback: &mut F)
where
F: FnMut(Range<usize>, Result<T, EscapeError>),
{
let mut chars = src.chars();
let allow_unicode_chars = mode.allow_unicode_chars(); // get this outside the loop
// The `start` and `end` computation here is complicated because
// `skip_ascii_whitespace` makes us to skip over chars without counting
// them in the range computation.
while let Some(c) = chars.next() {
let start = src.len() - chars.as_str().len() - c.len_utf8();
let res = match c {
'\\' => {
match chars.clone().next() {
Some('\n') => {
// Rust language specification requires us to skip whitespaces
// if unescaped '\' character is followed by '\n'.
// For details see [Rust language reference]
// (https://doc.rust-lang.org/reference/tokens.html#string-literals).
skip_ascii_whitespace(&mut chars, start, &mut |range, err| {
callback(range, Err(err))
});
continue;
}
_ => scan_escape::<T>(&mut chars, mode),
}
}
'"' => Err(EscapeError::EscapeOnlyChar),
'\r' => Err(EscapeError::BareCarriageReturn),
_ => ascii_check(c, allow_unicode_chars).map(T::from),
};
let end = src.len() - chars.as_str().len();
callback(start..end, res);
}
}
fn skip_ascii_whitespace<F>(chars: &mut Chars<'_>, start: usize, callback: &mut F)
where
F: FnMut(Range<usize>, EscapeError),
{
let tail = chars.as_str();
let first_non_space = tail
.bytes()
.position(|b| b != b' ' && b != b'\t' && b != b'\n' && b != b'\r')
.unwrap_or(tail.len());
if tail[1..first_non_space].contains('\n') {
// The +1 accounts for the escaping slash.
let end = start + first_non_space + 1;
callback(start..end, EscapeError::MultipleSkippedLinesWarning);
}
let tail = &tail[first_non_space..];
if let Some(c) = tail.chars().next() {
if c.is_whitespace() {
// For error reporting, we would like the span to contain the character that was not
// skipped. The +1 is necessary to account for the leading \ that started the escape.
let end = start + first_non_space + c.len_utf8() + 1;
callback(start..end, EscapeError::UnskippedWhitespaceWarning);
}
}
*chars = tail.chars();
}
/// Takes a contents of a string literal (without quotes) and produces a
/// sequence of characters or errors.
/// NOTE: Raw strings do not perform any explicit character escaping, here we
/// only produce errors on bare CR.
fn check_raw_common<F>(src: &str, mode: Mode, callback: &mut F)
where
F: FnMut(Range<usize>, Result<char, EscapeError>),
{
let mut chars = src.chars();
let allow_unicode_chars = mode.allow_unicode_chars(); // get this outside the loop
// The `start` and `end` computation here matches the one in
// `unescape_non_raw_common` for consistency, even though this function
// doesn't have to worry about skipping any chars.
while let Some(c) = chars.next() {
let start = src.len() - chars.as_str().len() - c.len_utf8();
let res = match c {
'\r' => Err(EscapeError::BareCarriageReturnInRawString),
_ => ascii_check(c, allow_unicode_chars),
};
let end = src.len() - chars.as_str().len();
callback(start..end, res);
}
}
#[inline]
pub fn byte_from_char(c: char) -> u8 {
let res = c as u32;
debug_assert!(res <= u8::MAX as u32, "guaranteed because of ByteStr");
res as u8
}

View file

@ -1,286 +0,0 @@
use super::*;
#[test]
fn test_unescape_char_bad() {
fn check(literal_text: &str, expected_error: EscapeError) {
assert_eq!(unescape_char(literal_text), Err(expected_error));
}
check("", EscapeError::ZeroChars);
check(r"\", EscapeError::LoneSlash);
check("\n", EscapeError::EscapeOnlyChar);
check("\t", EscapeError::EscapeOnlyChar);
check("'", EscapeError::EscapeOnlyChar);
check("\r", EscapeError::BareCarriageReturn);
check("spam", EscapeError::MoreThanOneChar);
check(r"\x0ff", EscapeError::MoreThanOneChar);
check(r#"\"a"#, EscapeError::MoreThanOneChar);
check(r"\na", EscapeError::MoreThanOneChar);
check(r"\ra", EscapeError::MoreThanOneChar);
check(r"\ta", EscapeError::MoreThanOneChar);
check(r"\\a", EscapeError::MoreThanOneChar);
check(r"\'a", EscapeError::MoreThanOneChar);
check(r"\0a", EscapeError::MoreThanOneChar);
check(r"\u{0}x", EscapeError::MoreThanOneChar);
check(r"\u{1F63b}}", EscapeError::MoreThanOneChar);
check(r"\v", EscapeError::InvalidEscape);
check(r"\💩", EscapeError::InvalidEscape);
check(r"\●", EscapeError::InvalidEscape);
check("\\\r", EscapeError::InvalidEscape);
check(r"\x", EscapeError::TooShortHexEscape);
check(r"\x0", EscapeError::TooShortHexEscape);
check(r"\xf", EscapeError::TooShortHexEscape);
check(r"\xa", EscapeError::TooShortHexEscape);
check(r"\xx", EscapeError::InvalidCharInHexEscape);
check(r"\xы", EscapeError::InvalidCharInHexEscape);
check(r"\x🦀", EscapeError::InvalidCharInHexEscape);
check(r"\xtt", EscapeError::InvalidCharInHexEscape);
check(r"\xff", EscapeError::OutOfRangeHexEscape);
check(r"\xFF", EscapeError::OutOfRangeHexEscape);
check(r"\x80", EscapeError::OutOfRangeHexEscape);
check(r"\u", EscapeError::NoBraceInUnicodeEscape);
check(r"\u[0123]", EscapeError::NoBraceInUnicodeEscape);
check(r"\u{0x}", EscapeError::InvalidCharInUnicodeEscape);
check(r"\u{", EscapeError::UnclosedUnicodeEscape);
check(r"\u{0000", EscapeError::UnclosedUnicodeEscape);
check(r"\u{}", EscapeError::EmptyUnicodeEscape);
check(r"\u{_0000}", EscapeError::LeadingUnderscoreUnicodeEscape);
check(r"\u{0000000}", EscapeError::OverlongUnicodeEscape);
check(r"\u{FFFFFF}", EscapeError::OutOfRangeUnicodeEscape);
check(r"\u{ffffff}", EscapeError::OutOfRangeUnicodeEscape);
check(r"\u{ffffff}", EscapeError::OutOfRangeUnicodeEscape);
check(r"\u{DC00}", EscapeError::LoneSurrogateUnicodeEscape);
check(r"\u{DDDD}", EscapeError::LoneSurrogateUnicodeEscape);
check(r"\u{DFFF}", EscapeError::LoneSurrogateUnicodeEscape);
check(r"\u{D800}", EscapeError::LoneSurrogateUnicodeEscape);
check(r"\u{DAAA}", EscapeError::LoneSurrogateUnicodeEscape);
check(r"\u{DBFF}", EscapeError::LoneSurrogateUnicodeEscape);
}
#[test]
fn test_unescape_char_good() {
fn check(literal_text: &str, expected_char: char) {
assert_eq!(unescape_char(literal_text), Ok(expected_char));
}
check("a", 'a');
check("ы", 'ы');
check("🦀", '🦀');
check(r#"\""#, '"');
check(r"\n", '\n');
check(r"\r", '\r');
check(r"\t", '\t');
check(r"\\", '\\');
check(r"\'", '\'');
check(r"\0", '\0');
check(r"\x00", '\0');
check(r"\x5a", 'Z');
check(r"\x5A", 'Z');
check(r"\x7f", 127 as char);
check(r"\u{0}", '\0');
check(r"\u{000000}", '\0');
check(r"\u{41}", 'A');
check(r"\u{0041}", 'A');
check(r"\u{00_41}", 'A');
check(r"\u{4__1__}", 'A');
check(r"\u{1F63b}", '😻');
}
#[test]
fn test_unescape_str_warn() {
fn check(literal: &str, expected: &[(Range<usize>, Result<char, EscapeError>)]) {
let mut unescaped = Vec::with_capacity(literal.len());
unescape_unicode(literal, Mode::Str, &mut |range, res| unescaped.push((range, res)));
assert_eq!(unescaped, expected);
}
// Check we can handle escaped newlines at the end of a file.
check("\\\n", &[]);
check("\\\n ", &[]);
check(
"\\\n \u{a0} x",
&[
(0..5, Err(EscapeError::UnskippedWhitespaceWarning)),
(3..5, Ok('\u{a0}')),
(5..6, Ok(' ')),
(6..7, Ok('x')),
],
);
check("\\\n \n x", &[(0..7, Err(EscapeError::MultipleSkippedLinesWarning)), (7..8, Ok('x'))]);
}
#[test]
fn test_unescape_str_good() {
fn check(literal_text: &str, expected: &str) {
let mut buf = Ok(String::with_capacity(literal_text.len()));
unescape_unicode(literal_text, Mode::Str, &mut |range, c| {
if let Ok(b) = &mut buf {
match c {
Ok(c) => b.push(c),
Err(e) => buf = Err((range, e)),
}
}
});
assert_eq!(buf.as_deref(), Ok(expected))
}
check("foo", "foo");
check("", "");
check(" \t\n", " \t\n");
check("hello \\\n world", "hello world");
check("thread's", "thread's")
}
#[test]
fn test_unescape_byte_bad() {
fn check(literal_text: &str, expected_error: EscapeError) {
assert_eq!(unescape_byte(literal_text), Err(expected_error));
}
check("", EscapeError::ZeroChars);
check(r"\", EscapeError::LoneSlash);
check("\n", EscapeError::EscapeOnlyChar);
check("\t", EscapeError::EscapeOnlyChar);
check("'", EscapeError::EscapeOnlyChar);
check("\r", EscapeError::BareCarriageReturn);
check("spam", EscapeError::MoreThanOneChar);
check(r"\x0ff", EscapeError::MoreThanOneChar);
check(r#"\"a"#, EscapeError::MoreThanOneChar);
check(r"\na", EscapeError::MoreThanOneChar);
check(r"\ra", EscapeError::MoreThanOneChar);
check(r"\ta", EscapeError::MoreThanOneChar);
check(r"\\a", EscapeError::MoreThanOneChar);
check(r"\'a", EscapeError::MoreThanOneChar);
check(r"\0a", EscapeError::MoreThanOneChar);
check(r"\v", EscapeError::InvalidEscape);
check(r"\💩", EscapeError::InvalidEscape);
check(r"\●", EscapeError::InvalidEscape);
check(r"\x", EscapeError::TooShortHexEscape);
check(r"\x0", EscapeError::TooShortHexEscape);
check(r"\xa", EscapeError::TooShortHexEscape);
check(r"\xf", EscapeError::TooShortHexEscape);
check(r"\xx", EscapeError::InvalidCharInHexEscape);
check(r"\xы", EscapeError::InvalidCharInHexEscape);
check(r"\x🦀", EscapeError::InvalidCharInHexEscape);
check(r"\xtt", EscapeError::InvalidCharInHexEscape);
check(r"\u", EscapeError::NoBraceInUnicodeEscape);
check(r"\u[0123]", EscapeError::NoBraceInUnicodeEscape);
check(r"\u{0x}", EscapeError::InvalidCharInUnicodeEscape);
check(r"\u{", EscapeError::UnclosedUnicodeEscape);
check(r"\u{0000", EscapeError::UnclosedUnicodeEscape);
check(r"\u{}", EscapeError::EmptyUnicodeEscape);
check(r"\u{_0000}", EscapeError::LeadingUnderscoreUnicodeEscape);
check(r"\u{0000000}", EscapeError::OverlongUnicodeEscape);
check("ы", EscapeError::NonAsciiCharInByte);
check("🦀", EscapeError::NonAsciiCharInByte);
check(r"\u{0}", EscapeError::UnicodeEscapeInByte);
check(r"\u{000000}", EscapeError::UnicodeEscapeInByte);
check(r"\u{41}", EscapeError::UnicodeEscapeInByte);
check(r"\u{0041}", EscapeError::UnicodeEscapeInByte);
check(r"\u{00_41}", EscapeError::UnicodeEscapeInByte);
check(r"\u{4__1__}", EscapeError::UnicodeEscapeInByte);
check(r"\u{1F63b}", EscapeError::UnicodeEscapeInByte);
check(r"\u{0}x", EscapeError::UnicodeEscapeInByte);
check(r"\u{1F63b}}", EscapeError::UnicodeEscapeInByte);
check(r"\u{FFFFFF}", EscapeError::UnicodeEscapeInByte);
check(r"\u{ffffff}", EscapeError::UnicodeEscapeInByte);
check(r"\u{ffffff}", EscapeError::UnicodeEscapeInByte);
check(r"\u{DC00}", EscapeError::UnicodeEscapeInByte);
check(r"\u{DDDD}", EscapeError::UnicodeEscapeInByte);
check(r"\u{DFFF}", EscapeError::UnicodeEscapeInByte);
check(r"\u{D800}", EscapeError::UnicodeEscapeInByte);
check(r"\u{DAAA}", EscapeError::UnicodeEscapeInByte);
check(r"\u{DBFF}", EscapeError::UnicodeEscapeInByte);
}
#[test]
fn test_unescape_byte_good() {
fn check(literal_text: &str, expected_byte: u8) {
assert_eq!(unescape_byte(literal_text), Ok(expected_byte));
}
check("a", b'a');
check(r#"\""#, b'"');
check(r"\n", b'\n');
check(r"\r", b'\r');
check(r"\t", b'\t');
check(r"\\", b'\\');
check(r"\'", b'\'');
check(r"\0", b'\0');
check(r"\x00", b'\0');
check(r"\x5a", b'Z');
check(r"\x5A", b'Z');
check(r"\x7f", 127);
check(r"\x80", 128);
check(r"\xff", 255);
check(r"\xFF", 255);
}
#[test]
fn test_unescape_byte_str_good() {
fn check(literal_text: &str, expected: &[u8]) {
let mut buf = Ok(Vec::with_capacity(literal_text.len()));
unescape_unicode(literal_text, Mode::ByteStr, &mut |range, c| {
if let Ok(b) = &mut buf {
match c {
Ok(c) => b.push(byte_from_char(c)),
Err(e) => buf = Err((range, e)),
}
}
});
assert_eq!(buf.as_deref(), Ok(expected))
}
check("foo", b"foo");
check("", b"");
check(" \t\n", b" \t\n");
check("hello \\\n world", b"hello world");
check("thread's", b"thread's")
}
#[test]
fn test_unescape_raw_str() {
fn check(literal: &str, expected: &[(Range<usize>, Result<char, EscapeError>)]) {
let mut unescaped = Vec::with_capacity(literal.len());
unescape_unicode(literal, Mode::RawStr, &mut |range, res| unescaped.push((range, res)));
assert_eq!(unescaped, expected);
}
check("\r", &[(0..1, Err(EscapeError::BareCarriageReturnInRawString))]);
check("\rx", &[(0..1, Err(EscapeError::BareCarriageReturnInRawString)), (1..2, Ok('x'))]);
}
#[test]
fn test_unescape_raw_byte_str() {
fn check(literal: &str, expected: &[(Range<usize>, Result<char, EscapeError>)]) {
let mut unescaped = Vec::with_capacity(literal.len());
unescape_unicode(literal, Mode::RawByteStr, &mut |range, res| unescaped.push((range, res)));
assert_eq!(unescaped, expected);
}
check("\r", &[(0..1, Err(EscapeError::BareCarriageReturnInRawString))]);
check("🦀", &[(0..4, Err(EscapeError::NonAsciiCharInByte))]);
check("🦀a", &[(0..4, Err(EscapeError::NonAsciiCharInByte)), (4..5, Ok('a'))]);
}

View file

@ -29,6 +29,7 @@ use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LocalDefId};
use rustc_hir::intravisit::FnKind as HirFnKind;
use rustc_hir::{Body, FnDecl, GenericParamKind, PatKind, PredicateOrigin};
use rustc_middle::bug;
use rustc_middle::lint::LevelAndSource;
use rustc_middle::ty::layout::LayoutOf;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, Upcast, VariantDef};
@ -694,7 +695,8 @@ impl<'tcx> LateLintPass<'tcx> for MissingDebugImplementations {
}
// Avoid listing trait impls if the trait is allowed.
let (level, _) = cx.tcx.lint_level_at_node(MISSING_DEBUG_IMPLEMENTATIONS, item.hir_id());
let LevelAndSource { level, .. } =
cx.tcx.lint_level_at_node(MISSING_DEBUG_IMPLEMENTATIONS, item.hir_id());
if level == Level::Allow {
return;
}

View file

@ -17,13 +17,12 @@ use rustc_hir::def_id::{CrateNum, DefId};
use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData};
use rustc_hir::{Pat, PatKind};
use rustc_middle::bug;
use rustc_middle::lint::LevelAndSource;
use rustc_middle::middle::privacy::EffectiveVisibilities;
use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout};
use rustc_middle::ty::print::{PrintError, PrintTraitRefExt as _, Printer, with_no_trimmed_paths};
use rustc_middle::ty::{self, GenericArg, RegisteredTools, Ty, TyCtxt, TypingEnv, TypingMode};
use rustc_session::lint::{
FutureIncompatibleInfo, Level, Lint, LintBuffer, LintExpectationId, LintId,
};
use rustc_session::lint::{FutureIncompatibleInfo, Lint, LintBuffer, LintExpectationId, LintId};
use rustc_session::{LintStoreMarker, Session};
use rustc_span::edit_distance::find_best_match_for_names;
use rustc_span::{Ident, Span, Symbol, sym};
@ -573,7 +572,7 @@ pub trait LintContext {
}
/// This returns the lint level for the given lint at the current location.
fn get_lint_level(&self, lint: &'static Lint) -> Level;
fn get_lint_level(&self, lint: &'static Lint) -> LevelAndSource;
/// This function can be used to manually fulfill an expectation. This can
/// be used for lints which contain several spans, and should be suppressed,
@ -642,8 +641,8 @@ impl<'tcx> LintContext for LateContext<'tcx> {
}
}
fn get_lint_level(&self, lint: &'static Lint) -> Level {
self.tcx.lint_level_at_node(lint, self.last_node_with_lint_attrs).0
fn get_lint_level(&self, lint: &'static Lint) -> LevelAndSource {
self.tcx.lint_level_at_node(lint, self.last_node_with_lint_attrs)
}
}
@ -663,8 +662,8 @@ impl LintContext for EarlyContext<'_> {
self.builder.opt_span_lint(lint, span.map(|s| s.into()), decorate)
}
fn get_lint_level(&self, lint: &'static Lint) -> Level {
self.builder.lint_level(lint).0
fn get_lint_level(&self, lint: &'static Lint) -> LevelAndSource {
self.builder.lint_level(lint)
}
}

View file

@ -84,10 +84,10 @@ impl LintLevelSets {
) -> LevelAndSource {
let lint = LintId::of(lint);
let (level, mut src) = self.raw_lint_id_level(lint, idx, aux);
let level = reveal_actual_level(level, &mut src, sess, lint, |id| {
let (level, lint_id) = reveal_actual_level(level, &mut src, sess, lint, |id| {
self.raw_lint_id_level(id, idx, aux)
});
(level, src)
LevelAndSource { level, lint_id, src }
}
fn raw_lint_id_level(
@ -95,17 +95,17 @@ impl LintLevelSets {
id: LintId,
mut idx: LintStackIndex,
aux: Option<&FxIndexMap<LintId, LevelAndSource>>,
) -> (Option<Level>, LintLevelSource) {
) -> (Option<(Level, Option<LintExpectationId>)>, LintLevelSource) {
if let Some(specs) = aux
&& let Some(&(level, src)) = specs.get(&id)
&& let Some(&LevelAndSource { level, lint_id, src }) = specs.get(&id)
{
return (Some(level), src);
return (Some((level, lint_id)), src);
}
loop {
let LintSet { ref specs, parent } = self.list[idx];
if let Some(&(level, src)) = specs.get(&id) {
return (Some(level), src);
if let Some(&LevelAndSource { level, lint_id, src }) = specs.get(&id) {
return (Some((level, lint_id)), src);
}
if idx == COMMAND_LINE {
return (None, LintLevelSource::Default);
@ -131,8 +131,8 @@ fn lints_that_dont_need_to_run(tcx: TyCtxt<'_>, (): ()) -> FxIndexSet<LintId> {
})
.filter_map(|lint| {
let lint_level = map.lint_level_id_at_node(tcx, LintId::of(lint), CRATE_HIR_ID);
if matches!(lint_level, (Level::Allow, ..))
|| (matches!(lint_level, (.., LintLevelSource::Default)))
if matches!(lint_level.level, Level::Allow)
|| (matches!(lint_level.src, LintLevelSource::Default))
&& lint.default_level(tcx.sess.edition()) == Level::Allow
{
Some(LintId::of(lint))
@ -379,13 +379,7 @@ impl<'tcx> Visitor<'tcx> for LintLevelMaximum<'tcx> {
fn visit_attribute(&mut self, attribute: &'tcx hir::Attribute) {
if matches!(
Level::from_attr(attribute),
Some(
Level::Warn
| Level::Deny
| Level::Forbid
| Level::Expect(..)
| Level::ForceWarn(..),
)
Some((Level::Warn | Level::Deny | Level::Forbid | Level::Expect | Level::ForceWarn, _))
) {
let store = unerased_lint_store(self.tcx.sess);
// Lint attributes are always a metalist inside a
@ -541,9 +535,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
for &(ref lint_name, level) in &self.sess.opts.lint_opts {
// Checks the validity of lint names derived from the command line.
let (tool_name, lint_name_only) = parse_lint_and_tool_name(lint_name);
if lint_name_only == crate::WARNINGS.name_lower()
&& matches!(level, Level::ForceWarn(_))
{
if lint_name_only == crate::WARNINGS.name_lower() && matches!(level, Level::ForceWarn) {
self.sess
.dcx()
.emit_err(UnsupportedGroup { lint_group: crate::WARNINGS.name_lower() });
@ -586,7 +578,6 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
_ => {}
};
let orig_level = level;
let lint_flag_val = Symbol::intern(lint_name);
let Ok(ids) = self.store.find_lints(lint_name) else {
@ -595,15 +586,15 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
};
for id in ids {
// ForceWarn and Forbid cannot be overridden
if let Some((Level::ForceWarn(_) | Level::Forbid, _)) =
if let Some(LevelAndSource { level: Level::ForceWarn | Level::Forbid, .. }) =
self.current_specs().get(&id)
{
continue;
}
if self.check_gated_lint(id, DUMMY_SP, true) {
let src = LintLevelSource::CommandLine(lint_flag_val, orig_level);
self.insert(id, (level, src));
let src = LintLevelSource::CommandLine(lint_flag_val, level);
self.insert(id, LevelAndSource { level, lint_id: None, src });
}
}
}
@ -612,8 +603,9 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
/// Attempts to insert the `id` to `level_src` map entry. If unsuccessful
/// (e.g. if a forbid was already inserted on the same scope), then emits a
/// diagnostic with no change to `specs`.
fn insert_spec(&mut self, id: LintId, (level, src): LevelAndSource) {
let (old_level, old_src) = self.provider.get_lint_level(id.lint, self.sess);
fn insert_spec(&mut self, id: LintId, LevelAndSource { level, lint_id, src }: LevelAndSource) {
let LevelAndSource { level: old_level, src: old_src, .. } =
self.provider.get_lint_level(id.lint, self.sess);
// Setting to a non-forbid level is an error if the lint previously had
// a forbid level. Note that this is not necessarily true even with a
@ -685,7 +677,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
// The lint `unfulfilled_lint_expectations` can't be expected, as it would suppress itself.
// Handling expectations of this lint would add additional complexity with little to no
// benefit. The expect level for this lint will therefore be ignored.
if let Level::Expect(_) = level
if let Level::Expect = level
&& id == LintId::of(UNFULFILLED_LINT_EXPECTATIONS)
{
return;
@ -693,13 +685,16 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
match (old_level, level) {
// If the new level is an expectation store it in `ForceWarn`
(Level::ForceWarn(_), Level::Expect(expectation_id)) => {
self.insert(id, (Level::ForceWarn(Some(expectation_id)), old_src))
(Level::ForceWarn, Level::Expect) => {
self.insert(id, LevelAndSource { level: Level::ForceWarn, lint_id, src: old_src })
}
// Keep `ForceWarn` level but drop the expectation
(Level::ForceWarn(_), _) => self.insert(id, (Level::ForceWarn(None), old_src)),
(Level::ForceWarn, _) => self.insert(
id,
LevelAndSource { level: Level::ForceWarn, lint_id: None, src: old_src },
),
// Set the lint level as normal
_ => self.insert(id, (level, src)),
_ => self.insert(id, LevelAndSource { level, lint_id, src }),
};
}
@ -714,7 +709,11 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
if attr.has_name(sym::automatically_derived) {
self.insert(
LintId::of(SINGLE_USE_LIFETIMES),
(Level::Allow, LintLevelSource::Default),
LevelAndSource {
level: Level::Allow,
lint_id: None,
src: LintLevelSource::Default,
},
);
continue;
}
@ -725,15 +724,22 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
.meta_item_list()
.is_some_and(|l| ast::attr::list_contains_name(&l, sym::hidden))
{
self.insert(LintId::of(MISSING_DOCS), (Level::Allow, LintLevelSource::Default));
self.insert(
LintId::of(MISSING_DOCS),
LevelAndSource {
level: Level::Allow,
lint_id: None,
src: LintLevelSource::Default,
},
);
continue;
}
let level = match Level::from_attr(attr) {
let (level, lint_id) = match Level::from_attr(attr) {
None => continue,
// This is the only lint level with a `LintExpectationId` that can be created from
// an attribute.
Some(Level::Expect(unstable_id)) if let Some(hir_id) = source_hir_id => {
Some((Level::Expect, Some(unstable_id))) if let Some(hir_id) = source_hir_id => {
let LintExpectationId::Unstable { lint_index: None, attr_id: _ } = unstable_id
else {
bug!("stable id Level::from_attr")
@ -745,9 +751,9 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
lint_index: None,
};
Level::Expect(stable_id)
(Level::Expect, Some(stable_id))
}
Some(lvl) => lvl,
Some((lvl, id)) => (lvl, id),
};
let Some(mut metas) = attr.meta_item_list() else { continue };
@ -795,13 +801,10 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
}
for (lint_index, li) in metas.iter_mut().enumerate() {
let level = match level {
Level::Expect(mut id) => {
id.set_lint_index(Some(lint_index as u16));
Level::Expect(id)
}
level => level,
};
let mut lint_id = lint_id;
if let Some(id) = &mut lint_id {
id.set_lint_index(Some(lint_index as u16));
}
let sp = li.span();
let meta_item = match li {
@ -933,7 +936,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
let src = LintLevelSource::Node { name, span: sp, reason };
for &id in ids {
if self.check_gated_lint(id, sp, false) {
self.insert_spec(id, (level, src));
self.insert_spec(id, LevelAndSource { level, lint_id, src });
}
}
@ -942,7 +945,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
// overriding the lint level but instead add an expectation that can't be
// fulfilled. The lint message will include an explanation, that the
// `unfulfilled_lint_expectations` lint can't be expected.
if let Level::Expect(expect_id) = level {
if let (Level::Expect, Some(expect_id)) = (level, lint_id) {
// The `unfulfilled_lint_expectations` lint is not part of any lint
// groups. Therefore. we only need to check the slice if it contains a
// single lint.
@ -964,7 +967,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
}
if self.lint_added_lints && !is_crate_node {
for (id, &(level, ref src)) in self.current_specs().iter() {
for (id, &LevelAndSource { level, ref src, .. }) in self.current_specs().iter() {
if !id.lint.crate_level_only {
continue;
}
@ -1002,10 +1005,10 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
if self.lint_added_lints {
let lint = builtin::UNKNOWN_LINTS;
let (level, src) = self.lint_level(builtin::UNKNOWN_LINTS);
let level = self.lint_level(builtin::UNKNOWN_LINTS);
// FIXME: make this translatable
#[allow(rustc::diagnostic_outside_of_impl)]
lint_level(self.sess, lint, level, src, Some(span.into()), |lint| {
lint_level(self.sess, lint, level, Some(span.into()), |lint| {
lint.primary_message(fluent::lint_unknown_gated_lint);
lint.arg("name", lint_id.lint.name_lower());
lint.note(fluent::lint_note);
@ -1040,8 +1043,8 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
span: Option<MultiSpan>,
decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
) {
let (level, src) = self.lint_level(lint);
lint_level(self.sess, lint, level, src, span, decorate)
let level = self.lint_level(lint);
lint_level(self.sess, lint, level, span, decorate)
}
#[track_caller]
@ -1051,16 +1054,16 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
span: MultiSpan,
decorate: impl for<'a> LintDiagnostic<'a, ()>,
) {
let (level, src) = self.lint_level(lint);
lint_level(self.sess, lint, level, src, Some(span), |lint| {
let level = self.lint_level(lint);
lint_level(self.sess, lint, level, Some(span), |lint| {
decorate.decorate_lint(lint);
});
}
#[track_caller]
pub fn emit_lint(&self, lint: &'static Lint, decorate: impl for<'a> LintDiagnostic<'a, ()>) {
let (level, src) = self.lint_level(lint);
lint_level(self.sess, lint, level, src, None, |lint| {
let level = self.lint_level(lint);
lint_level(self.sess, lint, level, None, |lint| {
decorate.decorate_lint(lint);
});
}

View file

@ -159,12 +159,13 @@ impl EarlyLintPass for NonAsciiIdents {
use rustc_span::Span;
use unicode_security::GeneralSecurityProfile;
let check_non_ascii_idents = cx.builder.lint_level(NON_ASCII_IDENTS).0 != Level::Allow;
let check_non_ascii_idents = cx.builder.lint_level(NON_ASCII_IDENTS).level != Level::Allow;
let check_uncommon_codepoints =
cx.builder.lint_level(UNCOMMON_CODEPOINTS).0 != Level::Allow;
let check_confusable_idents = cx.builder.lint_level(CONFUSABLE_IDENTS).0 != Level::Allow;
cx.builder.lint_level(UNCOMMON_CODEPOINTS).level != Level::Allow;
let check_confusable_idents =
cx.builder.lint_level(CONFUSABLE_IDENTS).level != Level::Allow;
let check_mixed_script_confusables =
cx.builder.lint_level(MIXED_SCRIPT_CONFUSABLES).0 != Level::Allow;
cx.builder.lint_level(MIXED_SCRIPT_CONFUSABLES).level != Level::Allow;
if !check_non_ascii_idents
&& !check_uncommon_codepoints

View file

@ -14,7 +14,7 @@ use rustc_middle::ty::{
};
use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass};
use rustc_span::def_id::LocalDefId;
use rustc_span::{Span, Symbol, source_map, sym};
use rustc_span::{Span, Symbol, sym};
use tracing::debug;
use {rustc_ast as ast, rustc_hir as hir};
@ -223,7 +223,7 @@ impl TypeLimits {
fn lint_nan<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx hir::Expr<'tcx>,
binop: hir::BinOp,
binop: hir::BinOpKind,
l: &'tcx hir::Expr<'tcx>,
r: &'tcx hir::Expr<'tcx>,
) {
@ -262,19 +262,19 @@ fn lint_nan<'tcx>(
InvalidNanComparisons::EqNe { suggestion }
}
let lint = match binop.node {
let lint = match binop {
hir::BinOpKind::Eq | hir::BinOpKind::Ne if is_nan(cx, l) => {
eq_ne(e, l, r, |l_span, r_span| InvalidNanComparisonsSuggestion::Spanful {
nan_plus_binop: l_span.until(r_span),
float: r_span.shrink_to_hi(),
neg: (binop.node == hir::BinOpKind::Ne).then(|| r_span.shrink_to_lo()),
neg: (binop == hir::BinOpKind::Ne).then(|| r_span.shrink_to_lo()),
})
}
hir::BinOpKind::Eq | hir::BinOpKind::Ne if is_nan(cx, r) => {
eq_ne(e, l, r, |l_span, r_span| InvalidNanComparisonsSuggestion::Spanful {
nan_plus_binop: l_span.shrink_to_hi().to(r_span),
float: l_span.shrink_to_hi(),
neg: (binop.node == hir::BinOpKind::Ne).then(|| l_span.shrink_to_lo()),
neg: (binop == hir::BinOpKind::Ne).then(|| l_span.shrink_to_lo()),
})
}
hir::BinOpKind::Lt | hir::BinOpKind::Le | hir::BinOpKind::Gt | hir::BinOpKind::Ge
@ -560,11 +560,11 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
}
}
hir::ExprKind::Binary(binop, ref l, ref r) => {
if is_comparison(binop) {
if !check_limits(cx, binop, l, r) {
if is_comparison(binop.node) {
if !check_limits(cx, binop.node, l, r) {
cx.emit_span_lint(UNUSED_COMPARISONS, e.span, UnusedComparisons);
} else {
lint_nan(cx, e, binop, l, r);
lint_nan(cx, e, binop.node, l, r);
let cmpop = ComparisonOp::BinOp(binop.node);
lint_wide_pointer(cx, e, cmpop, l, r);
lint_fn_pointer(cx, e, cmpop, l, r);
@ -591,8 +591,8 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
_ => {}
};
fn is_valid<T: PartialOrd>(binop: hir::BinOp, v: T, min: T, max: T) -> bool {
match binop.node {
fn is_valid<T: PartialOrd>(binop: hir::BinOpKind, v: T, min: T, max: T) -> bool {
match binop {
hir::BinOpKind::Lt => v > min && v <= max,
hir::BinOpKind::Le => v >= min && v < max,
hir::BinOpKind::Gt => v >= min && v < max,
@ -602,22 +602,19 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
}
}
fn rev_binop(binop: hir::BinOp) -> hir::BinOp {
source_map::respan(
binop.span,
match binop.node {
hir::BinOpKind::Lt => hir::BinOpKind::Gt,
hir::BinOpKind::Le => hir::BinOpKind::Ge,
hir::BinOpKind::Gt => hir::BinOpKind::Lt,
hir::BinOpKind::Ge => hir::BinOpKind::Le,
_ => return binop,
},
)
fn rev_binop(binop: hir::BinOpKind) -> hir::BinOpKind {
match binop {
hir::BinOpKind::Lt => hir::BinOpKind::Gt,
hir::BinOpKind::Le => hir::BinOpKind::Ge,
hir::BinOpKind::Gt => hir::BinOpKind::Lt,
hir::BinOpKind::Ge => hir::BinOpKind::Le,
_ => binop,
}
}
fn check_limits(
cx: &LateContext<'_>,
binop: hir::BinOp,
binop: hir::BinOpKind,
l: &hir::Expr<'_>,
r: &hir::Expr<'_>,
) -> bool {
@ -659,9 +656,9 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits {
}
}
fn is_comparison(binop: hir::BinOp) -> bool {
fn is_comparison(binop: hir::BinOpKind) -> bool {
matches!(
binop.node,
binop,
hir::BinOpKind::Eq
| hir::BinOpKind::Lt
| hir::BinOpKind::Le

View file

@ -8,7 +8,8 @@ use rustc_data_structures::stable_hasher::{
};
use rustc_error_messages::{DiagMessage, MultiSpan};
use rustc_hir::def::Namespace;
use rustc_hir::{HashStableContext, HirId, MissingLifetimeKind};
use rustc_hir::def_id::DefPathHash;
use rustc_hir::{HashStableContext, HirId, ItemLocalId, MissingLifetimeKind};
use rustc_macros::{Decodable, Encodable, HashStable_Generic};
pub use rustc_span::edition::Edition;
use rustc_span::{Ident, MacroRulesNormalizedIdent, Span, Symbol, sym};
@ -102,7 +103,7 @@ pub enum Applicability {
/// The index values have a type of `u16` to reduce the size of the `LintExpectationId`.
/// It's reasonable to assume that no user will define 2^16 attributes on one node or
/// have that amount of lints listed. `u16` values should therefore suffice.
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, Encodable, Decodable)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Encodable, Decodable)]
pub enum LintExpectationId {
/// Used for lints emitted during the `EarlyLintPass`. This id is not
/// hash stable and should not be cached.
@ -156,13 +157,14 @@ impl<HCX: rustc_hir::HashStableContext> HashStable<HCX> for LintExpectationId {
}
impl<HCX: rustc_hir::HashStableContext> ToStableHashKey<HCX> for LintExpectationId {
type KeyType = (HirId, u16, u16);
type KeyType = (DefPathHash, ItemLocalId, u16, u16);
#[inline]
fn to_stable_hash_key(&self, _: &HCX) -> Self::KeyType {
fn to_stable_hash_key(&self, hcx: &HCX) -> Self::KeyType {
match self {
LintExpectationId::Stable { hir_id, attr_index, lint_index: Some(lint_index) } => {
(*hir_id, *attr_index, *lint_index)
let (def_path_hash, lint_idx) = hir_id.to_stable_hash_key(hcx);
(def_path_hash, lint_idx, *attr_index, *lint_index)
}
_ => {
unreachable!("HashStable should only be called for a filled `LintExpectationId`")
@ -199,9 +201,9 @@ pub enum Level {
///
/// See RFC 2383.
///
/// The [`LintExpectationId`] is used to later link a lint emission to the actual
/// Requires a [`LintExpectationId`] to later link a lint emission to the actual
/// expectation. It can be ignored in most cases.
Expect(LintExpectationId),
Expect,
/// The `warn` level will produce a warning if the lint was violated, however the
/// compiler will continue with its execution.
Warn,
@ -209,9 +211,9 @@ pub enum Level {
/// to ensure that a lint can't be suppressed. This lint level can currently only be set
/// via the console and is therefore session specific.
///
/// The [`LintExpectationId`] is intended to fulfill expectations marked via the
/// Requires a [`LintExpectationId`] to fulfill expectations marked via the
/// `#[expect]` attribute, that will still be suppressed due to the level.
ForceWarn(Option<LintExpectationId>),
ForceWarn,
/// The `deny` level will produce an error and stop further execution after the lint
/// pass is complete.
Deny,
@ -225,9 +227,9 @@ impl Level {
pub fn as_str(self) -> &'static str {
match self {
Level::Allow => "allow",
Level::Expect(_) => "expect",
Level::Expect => "expect",
Level::Warn => "warn",
Level::ForceWarn(_) => "force-warn",
Level::ForceWarn => "force-warn",
Level::Deny => "deny",
Level::Forbid => "forbid",
}
@ -246,24 +248,30 @@ impl Level {
}
/// Converts an `Attribute` to a level.
pub fn from_attr(attr: &impl AttributeExt) -> Option<Self> {
pub fn from_attr(attr: &impl AttributeExt) -> Option<(Self, Option<LintExpectationId>)> {
Self::from_symbol(attr.name_or_empty(), || Some(attr.id()))
}
/// Converts a `Symbol` to a level.
pub fn from_symbol(s: Symbol, id: impl FnOnce() -> Option<AttrId>) -> Option<Self> {
pub fn from_symbol(
s: Symbol,
id: impl FnOnce() -> Option<AttrId>,
) -> Option<(Self, Option<LintExpectationId>)> {
match s {
sym::allow => Some(Level::Allow),
sym::allow => Some((Level::Allow, None)),
sym::expect => {
if let Some(attr_id) = id() {
Some(Level::Expect(LintExpectationId::Unstable { attr_id, lint_index: None }))
Some((
Level::Expect,
Some(LintExpectationId::Unstable { attr_id, lint_index: None }),
))
} else {
None
}
}
sym::warn => Some(Level::Warn),
sym::deny => Some(Level::Deny),
sym::forbid => Some(Level::Forbid),
sym::warn => Some((Level::Warn, None)),
sym::deny => Some((Level::Deny, None)),
sym::forbid => Some((Level::Forbid, None)),
_ => None,
}
}
@ -274,8 +282,8 @@ impl Level {
Level::Deny => "-D",
Level::Forbid => "-F",
Level::Allow => "-A",
Level::ForceWarn(_) => "--force-warn",
Level::Expect(_) => {
Level::ForceWarn => "--force-warn",
Level::Expect => {
unreachable!("the expect level does not have a commandline flag")
}
}
@ -283,17 +291,10 @@ impl Level {
pub fn is_error(self) -> bool {
match self {
Level::Allow | Level::Expect(_) | Level::Warn | Level::ForceWarn(_) => false,
Level::Allow | Level::Expect | Level::Warn | Level::ForceWarn => false,
Level::Deny | Level::Forbid => true,
}
}
pub fn get_expectation_id(&self) -> Option<LintExpectationId> {
match self {
Level::Expect(id) | Level::ForceWarn(Some(id)) => Some(*id),
_ => None,
}
}
}
/// Specification of a single lint.

View file

@ -47,7 +47,6 @@ struct LLVMRustMCDCBranchParameters {
int16_t ConditionIDs[2];
};
#if LLVM_VERSION_GE(19, 0)
static coverage::mcdc::BranchParameters
fromRust(LLVMRustMCDCBranchParameters Params) {
return coverage::mcdc::BranchParameters(
@ -59,7 +58,6 @@ fromRust(LLVMRustMCDCDecisionParameters Params) {
return coverage::mcdc::DecisionParameters(Params.BitmapIdx,
Params.NumConditions);
}
#endif
// Must match the layout of
// `rustc_codegen_llvm::coverageinfo::ffi::CoverageSpan`.
@ -203,7 +201,6 @@ extern "C" void LLVMRustCoverageWriteFunctionMappingsToBuffer(
Region.Span.LineEnd, Region.Span.ColumnEnd));
}
#if LLVM_VERSION_GE(19, 0)
// MC/DC branch regions:
for (const auto &Region : ArrayRef(MCDCBranchRegions, NumMCDCBranchRegions)) {
MappingRegions.push_back(coverage::CounterMappingRegion::makeBranchRegion(
@ -221,7 +218,6 @@ extern "C" void LLVMRustCoverageWriteFunctionMappingsToBuffer(
Region.Span.LineStart, Region.Span.ColumnStart, Region.Span.LineEnd,
Region.Span.ColumnEnd));
}
#endif
// Write the converted expressions and mappings to a byte buffer.
auto CoverageMappingWriter = coverage::CoverageMappingWriter(

Some files were not shown because too many files have changed in this diff Show more